From f980144e9c670a18119bf8127e184e82fa988f8b Mon Sep 17 00:00:00 2001 From: LIU Yuwei <22045841+Marsman1996@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:26:00 +0800 Subject: [PATCH 01/47] community: add init for unstructured file loader (#29101) ## Description Add `__init__` for unstructured loader of epub/image/markdown/pdf/ppt/word to restrict the input type to `str` or `Path`. In the [signature](https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.markdown.UnstructuredMarkdownLoader.html) these unstructured loaders receive `file_path: str | List[str] | Path | List[Path]`, but actually they only receive `str` or `Path`. ## Issue None ## Dependencies No changes. --- .../document_loaders/chm.py | 20 +++++++++- .../document_loaders/epub.py | 29 ++++++++++---- .../document_loaders/excel.py | 1 + .../document_loaders/image.py | 20 +++++++++- .../document_loaders/markdown.py | 38 ++++++++++++------- .../document_loaders/odt.py | 1 + .../document_loaders/org_mode.py | 1 + .../document_loaders/pdf.py | 17 +++++++++ .../document_loaders/powerpoint.py | 37 ++++++++++++------ .../document_loaders/rst.py | 1 + .../document_loaders/rtf.py | 11 ++---- .../document_loaders/tsv.py | 1 + .../document_loaders/word_document.py | 36 ++++++++++++------ 13 files changed, 157 insertions(+), 56 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/chm.py b/libs/community/langchain_community/document_loaders/chm.py index 207d31bd4f872..42ef6457bb8b7 100644 --- a/libs/community/langchain_community/document_loaders/chm.py +++ b/libs/community/langchain_community/document_loaders/chm.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Dict, List, Union +from pathlib import Path +from typing import TYPE_CHECKING, Any, Dict, List, Union from langchain_community.document_loaders.unstructured import UnstructuredFileLoader @@ -24,6 +25,23 @@ class UnstructuredCHMLoader(UnstructuredFileLoader): http://www.jedrea.com/chmlib/ """ + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the CHM file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> List: from unstructured.partition.html import partition_html diff --git a/libs/community/langchain_community/document_loaders/epub.py b/libs/community/langchain_community/document_loaders/epub.py index 6a3160691273f..d72fe0661e5c4 100644 --- a/libs/community/langchain_community/document_loaders/epub.py +++ b/libs/community/langchain_community/document_loaders/epub.py @@ -1,8 +1,9 @@ -from typing import List +from pathlib import Path +from typing import Any, List, Union from langchain_community.document_loaders.unstructured import ( UnstructuredFileLoader, - satisfies_min_unstructured_version, + validate_unstructured_version, ) @@ -30,13 +31,25 @@ class UnstructuredEPubLoader(UnstructuredFileLoader): https://unstructured-io.github.io/unstructured/bricks.html#partition-epub """ + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the EPub file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + validate_unstructured_version("0.5.4") + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> List: - min_unstructured_version = "0.5.4" - if not satisfies_min_unstructured_version(min_unstructured_version): - raise ValueError( - "Partitioning epub files is only supported in " - f"unstructured>={min_unstructured_version}." - ) from unstructured.partition.epub import partition_epub return partition_epub(filename=self.file_path, **self.unstructured_kwargs) # type: ignore[arg-type] diff --git a/libs/community/langchain_community/document_loaders/excel.py b/libs/community/langchain_community/document_loaders/excel.py index ace4eeee6467c..aa28d89110011 100644 --- a/libs/community/langchain_community/document_loaders/excel.py +++ b/libs/community/langchain_community/document_loaders/excel.py @@ -42,6 +42,7 @@ def __init__( for more info. Optional. Defaults to "single". **unstructured_kwargs: Keyword arguments to pass to unstructured. """ + file_path = str(file_path) validate_unstructured_version(min_unstructured_version="0.6.7") super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) diff --git a/libs/community/langchain_community/document_loaders/image.py b/libs/community/langchain_community/document_loaders/image.py index c574226c753ab..63a89e71ccbb4 100644 --- a/libs/community/langchain_community/document_loaders/image.py +++ b/libs/community/langchain_community/document_loaders/image.py @@ -1,4 +1,5 @@ -from typing import List +from pathlib import Path +from typing import Any, List, Union from langchain_community.document_loaders.unstructured import UnstructuredFileLoader @@ -27,6 +28,23 @@ class UnstructuredImageLoader(UnstructuredFileLoader): https://unstructured-io.github.io/unstructured/bricks.html#partition-image """ + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the Image file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> List: from unstructured.partition.image import partition_image diff --git a/libs/community/langchain_community/document_loaders/markdown.py b/libs/community/langchain_community/document_loaders/markdown.py index f204b0bdc0349..3c3196c2cb441 100644 --- a/libs/community/langchain_community/document_loaders/markdown.py +++ b/libs/community/langchain_community/document_loaders/markdown.py @@ -1,6 +1,10 @@ -from typing import List +from pathlib import Path +from typing import Any, List, Union -from langchain_community.document_loaders.unstructured import UnstructuredFileLoader +from langchain_community.document_loaders.unstructured import ( + UnstructuredFileLoader, + validate_unstructured_version, +) class UnstructuredMarkdownLoader(UnstructuredFileLoader): @@ -68,19 +72,25 @@ class UnstructuredMarkdownLoader(UnstructuredFileLoader): https://unstructured-io.github.io/unstructured/core/partition.html#partition-md """ # noqa: E501 + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the Markdown file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + validate_unstructured_version("0.4.16") + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> List: - from unstructured.__version__ import __version__ as __unstructured_version__ from unstructured.partition.md import partition_md - # NOTE(MthwRobinson) - enables the loader to work when you're using pre-release - # versions of unstructured like 0.4.17-dev1 - _unstructured_version = __unstructured_version__.split("-")[0] - unstructured_version = tuple([int(x) for x in _unstructured_version.split(".")]) - - if unstructured_version < (0, 4, 16): - raise ValueError( - f"You are on unstructured version {__unstructured_version__}. " - "Partitioning markdown files is only supported in unstructured>=0.4.16." - ) - return partition_md(filename=self.file_path, **self.unstructured_kwargs) # type: ignore[arg-type] diff --git a/libs/community/langchain_community/document_loaders/odt.py b/libs/community/langchain_community/document_loaders/odt.py index a803aeafcbeca..7d1236ff88112 100644 --- a/libs/community/langchain_community/document_loaders/odt.py +++ b/libs/community/langchain_community/document_loaders/odt.py @@ -45,6 +45,7 @@ def __init__( "multi", or "all". Default is "single". **unstructured_kwargs: Any kwargs to pass to the unstructured. """ + file_path = str(file_path) validate_unstructured_version(min_unstructured_version="0.6.3") super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) diff --git a/libs/community/langchain_community/document_loaders/org_mode.py b/libs/community/langchain_community/document_loaders/org_mode.py index 106bf58da48d7..b8ba182843377 100644 --- a/libs/community/langchain_community/document_loaders/org_mode.py +++ b/libs/community/langchain_community/document_loaders/org_mode.py @@ -45,6 +45,7 @@ def __init__( **unstructured_kwargs: Any additional keyword arguments to pass to the unstructured. """ + file_path = str(file_path) validate_unstructured_version(min_unstructured_version="0.7.9") super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) diff --git a/libs/community/langchain_community/document_loaders/pdf.py b/libs/community/langchain_community/document_loaders/pdf.py index c8ee848a733a9..25b3e72a3d8a1 100644 --- a/libs/community/langchain_community/document_loaders/pdf.py +++ b/libs/community/langchain_community/document_loaders/pdf.py @@ -68,6 +68,23 @@ class UnstructuredPDFLoader(UnstructuredFileLoader): https://unstructured-io.github.io/unstructured/bricks.html#partition-pdf """ + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the PDF file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> list: from unstructured.partition.pdf import partition_pdf diff --git a/libs/community/langchain_community/document_loaders/powerpoint.py b/libs/community/langchain_community/document_loaders/powerpoint.py index ed360e94f903d..0fecbe4b67848 100644 --- a/libs/community/langchain_community/document_loaders/powerpoint.py +++ b/libs/community/langchain_community/document_loaders/powerpoint.py @@ -1,7 +1,11 @@ import os -from typing import List +from pathlib import Path +from typing import Any, List, Union -from langchain_community.document_loaders.unstructured import UnstructuredFileLoader +from langchain_community.document_loaders.unstructured import ( + UnstructuredFileLoader, + validate_unstructured_version, +) class UnstructuredPowerPointLoader(UnstructuredFileLoader): @@ -29,13 +33,26 @@ class UnstructuredPowerPointLoader(UnstructuredFileLoader): https://unstructured-io.github.io/unstructured/bricks.html#partition-pptx """ + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the PowerPoint file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> List: - from unstructured.__version__ import __version__ as __unstructured_version__ from unstructured.file_utils.filetype import FileType, detect_filetype - unstructured_version = tuple( - [int(x) for x in __unstructured_version__.split(".")] - ) # NOTE(MthwRobinson) - magic will raise an import error if the libmagic # system dependency isn't installed. If it's not installed, we'll just # check the file extension @@ -47,12 +64,8 @@ def _get_elements(self) -> List: _, extension = os.path.splitext(str(self.file_path)) is_ppt = extension == ".ppt" - if is_ppt and unstructured_version < (0, 4, 11): - raise ValueError( - f"You are on unstructured version {__unstructured_version__}. " - "Partitioning .ppt files is only supported in unstructured>=0.4.11. " - "Please upgrade the unstructured package and try again." - ) + if is_ppt: + validate_unstructured_version("0.4.11") if is_ppt: from unstructured.partition.ppt import partition_ppt diff --git a/libs/community/langchain_community/document_loaders/rst.py b/libs/community/langchain_community/document_loaders/rst.py index 310f1bb7c6086..77e4fc38b9194 100644 --- a/libs/community/langchain_community/document_loaders/rst.py +++ b/libs/community/langchain_community/document_loaders/rst.py @@ -49,6 +49,7 @@ def __init__( **unstructured_kwargs: Additional keyword arguments to pass to unstructured. """ + file_path = str(file_path) validate_unstructured_version(min_unstructured_version="0.7.5") super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) diff --git a/libs/community/langchain_community/document_loaders/rtf.py b/libs/community/langchain_community/document_loaders/rtf.py index 63128aa837224..bbecd7f455ddc 100644 --- a/libs/community/langchain_community/document_loaders/rtf.py +++ b/libs/community/langchain_community/document_loaders/rtf.py @@ -5,7 +5,7 @@ from langchain_community.document_loaders.unstructured import ( UnstructuredFileLoader, - satisfies_min_unstructured_version, + validate_unstructured_version, ) @@ -49,13 +49,8 @@ def __init__( **unstructured_kwargs: Additional keyword arguments to pass to unstructured. """ - min_unstructured_version = "0.5.12" - if not satisfies_min_unstructured_version(min_unstructured_version): - raise ValueError( - "Partitioning rtf files is only supported in " - f"unstructured>={min_unstructured_version}." - ) - + file_path = str(file_path) + validate_unstructured_version("0.5.12") super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) def _get_elements(self) -> List: diff --git a/libs/community/langchain_community/document_loaders/tsv.py b/libs/community/langchain_community/document_loaders/tsv.py index f6d4a085ce7af..a9455fd06f48b 100644 --- a/libs/community/langchain_community/document_loaders/tsv.py +++ b/libs/community/langchain_community/document_loaders/tsv.py @@ -32,6 +32,7 @@ def __init__( mode: str = "single", **unstructured_kwargs: Any, ): + file_path = str(file_path) validate_unstructured_version(min_unstructured_version="0.7.6") super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) diff --git a/libs/community/langchain_community/document_loaders/word_document.py b/libs/community/langchain_community/document_loaders/word_document.py index bd2c4ae8f57f8..eac96eb5faa83 100644 --- a/libs/community/langchain_community/document_loaders/word_document.py +++ b/libs/community/langchain_community/document_loaders/word_document.py @@ -4,14 +4,17 @@ import tempfile from abc import ABC from pathlib import Path -from typing import List, Union +from typing import Any, List, Union from urllib.parse import urlparse import requests from langchain_core.documents import Document from langchain_community.document_loaders.base import BaseLoader -from langchain_community.document_loaders.unstructured import UnstructuredFileLoader +from langchain_community.document_loaders.unstructured import ( + UnstructuredFileLoader, + validate_unstructured_version, +) class Docx2txtLoader(BaseLoader, ABC): @@ -92,13 +95,26 @@ class UnstructuredWordDocumentLoader(UnstructuredFileLoader): https://unstructured-io.github.io/unstructured/bricks.html#partition-docx """ + def __init__( + self, + file_path: Union[str, Path], + mode: str = "single", + **unstructured_kwargs: Any, + ): + """ + + Args: + file_path: The path to the Word file to load. + mode: The mode to use when loading the file. Can be one of "single", + "multi", or "all". Default is "single". + **unstructured_kwargs: Any kwargs to pass to the unstructured. + """ + file_path = str(file_path) + super().__init__(file_path=file_path, mode=mode, **unstructured_kwargs) + def _get_elements(self) -> List: - from unstructured.__version__ import __version__ as __unstructured_version__ from unstructured.file_utils.filetype import FileType, detect_filetype - unstructured_version = tuple( - [int(x) for x in __unstructured_version__.split(".")] - ) # NOTE(MthwRobinson) - magic will raise an import error if the libmagic # system dependency isn't installed. If it's not installed, we'll just # check the file extension @@ -110,12 +126,8 @@ def _get_elements(self) -> List: _, extension = os.path.splitext(str(self.file_path)) is_doc = extension == ".doc" - if is_doc and unstructured_version < (0, 4, 11): - raise ValueError( - f"You are on unstructured version {__unstructured_version__}. " - "Partitioning .doc files is only supported in unstructured>=0.4.11. " - "Please upgrade the unstructured package and try again." - ) + if is_doc: + validate_unstructured_version("0.4.11") if is_doc: from unstructured.partition.doc import partition_doc From 349b5c91c2a5d2da6c261b40dafdd31401e5c494 Mon Sep 17 00:00:00 2001 From: ThomasSaulou <54414107+ThomasSaulou@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:31:37 +0100 Subject: [PATCH 02/47] fix chatperplexity: remove 'stream' from params in _stream method (#29173) quick fix chatperplexity: remove 'stream' from params in _stream method --- libs/community/langchain_community/chat_models/perplexity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/chat_models/perplexity.py b/libs/community/langchain_community/chat_models/perplexity.py index d168b1363e56f..f5a58def0e9d2 100644 --- a/libs/community/langchain_community/chat_models/perplexity.py +++ b/libs/community/langchain_community/chat_models/perplexity.py @@ -217,7 +217,7 @@ def _stream( message_dicts, params = self._create_message_dicts(messages, stop) params = {**params, **kwargs} default_chunk_class = AIMessageChunk - + params.pop("stream", None) if stop: params["stop_sequences"] = stop stream_resp = self.client.chat.completions.create( From c115c09b6d792de6c374a38d3e45e20edaf8819a Mon Sep 17 00:00:00 2001 From: Priyansh Agrawal Date: Mon, 13 Jan 2025 20:02:57 +0530 Subject: [PATCH 03/47] community: add missing format specifier in error log in CubeSemanticLoader (#29172) Thank you for contributing to LangChain! - [x] **PR title**: "package: description" - Where "package" is whichever of langchain, community, core, etc. is being modified. Use "docs: ..." for purely docs changes, "infra: ..." for CI changes. - Example: "community: add foobar LLM" - [x] **PR message** - **Description:** Add a missing format specifier in an an error log in `langchain_community.document_loaders.CubeSemanticLoader` - **Issue:** raises `TypeError: not all arguments converted during string formatting` - [ ] **Add tests and docs**: If you're adding a new integration, please include 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/docs/integrations` directory. - [ ] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. See contribution guidelines for more: https://python.langchain.com/docs/contributing/ Additional guidelines: - Make sure optional dependencies are imported within a function. - Please do not add dependencies to pyproject.toml files (even optional ones) unless they are required for unit tests. - Most PRs should not touch more than one package. - Changes should be backwards compatible. - If you are adding something to community, do not re-import it in langchain. If no one reviews your PR within a few days, please @-mention one of baskaryan, efriis, eyurtsev, ccurme, vbarda, hwchase17. --- .../langchain_community/document_loaders/cube_semantic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/document_loaders/cube_semantic.py b/libs/community/langchain_community/document_loaders/cube_semantic.py index 81a35787edde6..7d7487394cb95 100644 --- a/libs/community/langchain_community/document_loaders/cube_semantic.py +++ b/libs/community/langchain_community/document_loaders/cube_semantic.py @@ -92,7 +92,9 @@ def _get_dimension_values(self, dimension_name: str) -> List[str]: ] return dimension_values else: - logger.error("Request failed with status code:", response.status_code) + logger.error( + "Request failed with status code: %s", response.status_code + ) break if retries == self.dimension_values_max_retries: From 8ef7f3eacc006bb55dbaed90d24a7f62a80873a4 Mon Sep 17 00:00:00 2001 From: Syed Muneeb Abbas <130203424+syedmuneeb321@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:47:31 +0500 Subject: [PATCH 04/47] =?UTF-8?q?Fixed=20the=20import=20error=20in=20OpenA?= =?UTF-8?q?IWhisperParserLocal=20and=20resolved=20the=20L=E2=80=A6=20(#291?= =?UTF-8?q?68)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …angChain parser issue. Thank you for contributing to LangChain! - [ ] **PR title**: "package: description" - Where "package" is whichever of langchain, community, core, etc. is being modified. Use "docs: ..." for purely docs changes, "infra: ..." for CI changes. - Example: "community: add foobar LLM" - [ ] **PR message**: ***Delete this entire checklist*** and replace with - **Description:** a description of the change - **Issue:** the issue # it fixes, if applicable - **Dependencies:** any dependencies required for this change - **Twitter handle:** if your PR gets announced, and you'd like a mention, we'll gladly shout you out! - [ ] **Add tests and docs**: If you're adding a new integration, please include 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/docs/integrations` directory. - [ ] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. See contribution guidelines for more: https://python.langchain.com/docs/contributing/ Additional guidelines: - Make sure optional dependencies are imported within a function. - Please do not add dependencies to pyproject.toml files (even optional ones) unless they are required for unit tests. - Most PRs should not touch more than one package. - Changes should be backwards compatible. - If you are adding something to community, do not re-import it in langchain. If no one reviews your PR within a few days, please @-mention one of baskaryan, efriis, eyurtsev, ccurme, vbarda, hwchase17. --- .../document_loaders/youtube_audio.ipynb | 660 ++++++++++-------- 1 file changed, 351 insertions(+), 309 deletions(-) diff --git a/docs/docs/integrations/document_loaders/youtube_audio.ipynb b/docs/docs/integrations/document_loaders/youtube_audio.ipynb index 7a34546aabb26..31ef623baaf86 100644 --- a/docs/docs/integrations/document_loaders/youtube_audio.ipynb +++ b/docs/docs/integrations/document_loaders/youtube_audio.ipynb @@ -1,321 +1,363 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "e48afb8d", - "metadata": {}, - "source": [ - "# YouTube audio\n", - "\n", - "Building chat or QA applications on YouTube videos is a topic of high interest.\n", - "\n", - "Below we show how to easily go from a `YouTube url` to `audio of the video` to `text` to `chat`!\n", - "\n", - "We wil use the `OpenAIWhisperParser`, which will use the OpenAI Whisper API to transcribe audio to text, \n", - "and the `OpenAIWhisperParserLocal` for local support and running on private clouds or on premise.\n", - "\n", - "Note: You will need to have an `OPENAI_API_KEY` supplied." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "5f34e934", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_community.document_loaders.blob_loaders.youtube_audio import (\n", - " YoutubeAudioLoader,\n", - ")\n", - "from langchain_community.document_loaders.generic import GenericLoader\n", - "from langchain_community.document_loaders.parsers import (\n", - " OpenAIWhisperParser,\n", - " OpenAIWhisperParserLocal,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "85fc12bd", - "metadata": {}, - "source": [ - "We will use `yt_dlp` to download audio for YouTube urls.\n", - "\n", - "We will use `pydub` to split downloaded audio files (such that we adhere to Whisper API's 25MB file size limit)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fb5a6606", - "metadata": {}, - "outputs": [], - "source": [ - "%pip install --upgrade --quiet yt_dlp\n", - "%pip install --upgrade --quiet pydub\n", - "%pip install --upgrade --quiet librosa" - ] - }, - { - "cell_type": "markdown", - "id": "b0e119f4", - "metadata": {}, - "source": [ - "### YouTube url to text\n", - "\n", - "Use `YoutubeAudioLoader` to fetch / download the audio files.\n", - "\n", - "Then, ues `OpenAIWhisperParser()` to transcribe them to text.\n", - "\n", - "Let's take the first lecture of Andrej Karpathy's YouTube course as an example! " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8682f256", - "metadata": {}, - "outputs": [], - "source": [ - "# set a flag to switch between local and remote parsing\n", - "# change this to True if you want to use local parsing\n", - "local = False" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "23e1e134", - "metadata": {}, - "outputs": [ + "cells": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "[youtube] Extracting URL: https://youtu.be/kCc8FmEb1nY\n", - "[youtube] kCc8FmEb1nY: Downloading webpage\n", - "[youtube] kCc8FmEb1nY: Downloading android player API JSON\n", - "[info] kCc8FmEb1nY: Downloading 1 format(s): 140\n", - "[dashsegments] Total fragments: 11\n", - "[download] Destination: /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/Let's build GPT: from scratch, in code, spelled out..m4a\n", - "[download] 100% of 107.73MiB in 00:00:18 at 5.92MiB/s \n", - "[FixupM4a] Correcting container of \"/Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/Let's build GPT: from scratch, in code, spelled out..m4a\"\n", - "[ExtractAudio] Not converting audio /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/Let's build GPT: from scratch, in code, spelled out..m4a; file is already in target format m4a\n", - "[youtube] Extracting URL: https://youtu.be/VMj-3S1tku0\n", - "[youtube] VMj-3S1tku0: Downloading webpage\n", - "[youtube] VMj-3S1tku0: Downloading android player API JSON\n", - "[info] VMj-3S1tku0: Downloading 1 format(s): 140\n", - "[download] /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/The spelled-out intro to neural networks and backpropagation: building micrograd.m4a has already been downloaded\n", - "[download] 100% of 134.98MiB\n", - "[ExtractAudio] Not converting audio /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/The spelled-out intro to neural networks and backpropagation: building micrograd.m4a; file is already in target format m4a\n" - ] - } - ], - "source": [ - "# Two Karpathy lecture videos\n", - "urls = [\"https://youtu.be/kCc8FmEb1nY\", \"https://youtu.be/VMj-3S1tku0\"]\n", - "\n", - "# Directory to save audio files\n", - "save_dir = \"~/Downloads/YouTube\"\n", - "\n", - "# Transcribe the videos to text\n", - "if local:\n", - " loader = GenericLoader(\n", - " YoutubeAudioLoader(urls, save_dir), OpenAIWhisperParserLocal()\n", - " )\n", - "else:\n", - " loader = GenericLoader(YoutubeAudioLoader(urls, save_dir), OpenAIWhisperParser())\n", - "docs = loader.load()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "72a94fd8", - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "id": "e48afb8d", + "metadata": { + "id": "e48afb8d" + }, + "source": [ + "# YouTube audio\n", + "\n", + "Building chat or QA applications on YouTube videos is a topic of high interest.\n", + "\n", + "Below we show how to easily go from a `YouTube url` to `audio of the video` to `text` to `chat`!\n", + "\n", + "We wil use the `OpenAIWhisperParser`, which will use the OpenAI Whisper API to transcribe audio to text,\n", + "and the `OpenAIWhisperParserLocal` for local support and running on private clouds or on premise.\n", + "\n", + "Note: You will need to have an `OPENAI_API_KEY` supplied." + ] + }, { - "data": { - "text/plain": [ - "\"Hello, my name is Andrej and I've been training deep neural networks for a bit more than a decade. And in this lecture I'd like to show you what neural network training looks like under the hood. So in particular we are going to start with a blank Jupyter notebook and by the end of this lecture we will define and train a neural net and you'll get to see everything that goes on under the hood and exactly sort of how that works on an intuitive level. Now specifically what I would like to do is I w\"" + "cell_type": "code", + "execution_count": null, + "id": "5f34e934", + "metadata": { + "id": "5f34e934" + }, + "outputs": [], + "source": [ + "from langchain_community.document_loaders.blob_loaders.youtube_audio import (\n", + " YoutubeAudioLoader,\n", + ")\n", + "from langchain_community.document_loaders.generic import GenericLoader\n", + "from langchain_community.document_loaders.parsers.audio import (\n", + " OpenAIWhisperParser,\n", + " OpenAIWhisperParserLocal,\n", + ")" ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Returns a list of Documents, which can be easily viewed or parsed\n", - "docs[0].page_content[0:500]" - ] - }, - { - "cell_type": "markdown", - "id": "93be6b49", - "metadata": {}, - "source": [ - "### Building a chat app from YouTube video\n", - "\n", - "Given `Documents`, we can easily enable chat / question+answering." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "1823f042", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.chains import RetrievalQA\n", - "from langchain_community.vectorstores import FAISS\n", - "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", - "from langchain_text_splitters import RecursiveCharacterTextSplitter" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7257cda1", - "metadata": {}, - "outputs": [], - "source": [ - "# Combine doc\n", - "combined_docs = [doc.page_content for doc in docs]\n", - "text = \" \".join(combined_docs)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "147c0c55", - "metadata": {}, - "outputs": [], - "source": [ - "# Split them\n", - "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=150)\n", - "splits = text_splitter.split_text(text)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f3556703", - "metadata": {}, - "outputs": [], - "source": [ - "# Build an index\n", - "embeddings = OpenAIEmbeddings()\n", - "vectordb = FAISS.from_texts(splits, embeddings)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "beaa99db", - "metadata": {}, - "outputs": [], - "source": [ - "# Build a QA chain\n", - "qa_chain = RetrievalQA.from_chain_type(\n", - " llm=ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0),\n", - " chain_type=\"stuff\",\n", - " retriever=vectordb.as_retriever(),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "f2239a62", - "metadata": {}, - "outputs": [ + }, { - "data": { - "text/plain": [ - "\"We need to zero out the gradient before backprop at each step because the backward pass accumulates gradients in the grad attribute of each parameter. If we don't reset the grad to zero before each backward pass, the gradients will accumulate and add up, leading to incorrect updates and slower convergence. By resetting the grad to zero before each backward pass, we ensure that the gradients are calculated correctly and that the optimization process works as intended.\"" + "cell_type": "markdown", + "id": "85fc12bd", + "metadata": { + "id": "85fc12bd" + }, + "source": [ + "We will use `yt_dlp` to download audio for YouTube urls.\n", + "\n", + "We will use `pydub` to split downloaded audio files (such that we adhere to Whisper API's 25MB file size limit)." ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Ask a question!\n", - "query = \"Why do we need to zero out the gradient before backprop at each step?\"\n", - "qa_chain.run(query)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a8d01098", - "metadata": {}, - "outputs": [ + }, { - "data": { - "text/plain": [ - "'In the context of transformers, an encoder is a component that reads in a sequence of input tokens and generates a sequence of hidden representations. On the other hand, a decoder is a component that takes in a sequence of hidden representations and generates a sequence of output tokens. The main difference between the two is that the encoder is used to encode the input sequence into a fixed-length representation, while the decoder is used to decode the fixed-length representation into an output sequence. In machine translation, for example, the encoder reads in the source language sentence and generates a fixed-length representation, which is then used by the decoder to generate the target language sentence.'" + "cell_type": "code", + "execution_count": null, + "id": "fb5a6606", + "metadata": { + "id": "fb5a6606" + }, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet yt_dlp\n", + "%pip install --upgrade --quiet pydub\n", + "%pip install --upgrade --quiet librosa" ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "query = \"What is the difference between an encoder and decoder?\"\n", - "qa_chain.run(query)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "fe1e77dd", - "metadata": {}, - "outputs": [ + }, { - "data": { - "text/plain": [ - "'For any token, x is the input vector that contains the private information of that token, k and q are the key and query vectors respectively, which are produced by forwarding linear modules on x, and v is the vector that is calculated by propagating the same linear module on x again. The key vector represents what the token contains, and the query vector represents what the token is looking for. The vector v is the information that the token will communicate to other tokens if it finds them interesting, and it gets aggregated for the purposes of the self-attention mechanism.'" + "cell_type": "markdown", + "id": "b0e119f4", + "metadata": { + "id": "b0e119f4" + }, + "source": [ + "### YouTube url to text\n", + "\n", + "Use `YoutubeAudioLoader` to fetch / download the audio files.\n", + "\n", + "Then, ues `OpenAIWhisperParser()` to transcribe them to text.\n", + "\n", + "Let's take the first lecture of Andrej Karpathy's YouTube course as an example!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8682f256", + "metadata": { + "id": "8682f256" + }, + "outputs": [], + "source": [ + "# set a flag to switch between local and remote parsing\n", + "# change this to True if you want to use local parsing\n", + "local = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23e1e134", + "metadata": { + "id": "23e1e134", + "outputId": "0794ffeb-f912-48cc-e3cb-3b4d6e5221c7" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[youtube] Extracting URL: https://youtu.be/kCc8FmEb1nY\n", + "[youtube] kCc8FmEb1nY: Downloading webpage\n", + "[youtube] kCc8FmEb1nY: Downloading android player API JSON\n", + "[info] kCc8FmEb1nY: Downloading 1 format(s): 140\n", + "[dashsegments] Total fragments: 11\n", + "[download] Destination: /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/Let's build GPT: from scratch, in code, spelled out..m4a\n", + "[download] 100% of 107.73MiB in 00:00:18 at 5.92MiB/s \n", + "[FixupM4a] Correcting container of \"/Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/Let's build GPT: from scratch, in code, spelled out..m4a\"\n", + "[ExtractAudio] Not converting audio /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/Let's build GPT: from scratch, in code, spelled out..m4a; file is already in target format m4a\n", + "[youtube] Extracting URL: https://youtu.be/VMj-3S1tku0\n", + "[youtube] VMj-3S1tku0: Downloading webpage\n", + "[youtube] VMj-3S1tku0: Downloading android player API JSON\n", + "[info] VMj-3S1tku0: Downloading 1 format(s): 140\n", + "[download] /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/The spelled-out intro to neural networks and backpropagation: building micrograd.m4a has already been downloaded\n", + "[download] 100% of 134.98MiB\n", + "[ExtractAudio] Not converting audio /Users/31treehaus/Desktop/AI/langchain-fork/docs/modules/indexes/document_loaders/examples/The spelled-out intro to neural networks and backpropagation: building micrograd.m4a; file is already in target format m4a\n" + ] + } + ], + "source": [ + "# Two Karpathy lecture videos\n", + "urls = [\"https://youtu.be/kCc8FmEb1nY\", \"https://youtu.be/VMj-3S1tku0\"]\n", + "\n", + "# Directory to save audio files\n", + "save_dir = \"~/Downloads/YouTube\"\n", + "\n", + "# Transcribe the videos to text\n", + "if local:\n", + " loader = GenericLoader(\n", + " YoutubeAudioLoader(urls, save_dir), OpenAIWhisperParserLocal()\n", + " )\n", + "else:\n", + " loader = GenericLoader(YoutubeAudioLoader(urls, save_dir), OpenAIWhisperParser())\n", + "docs = loader.load()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72a94fd8", + "metadata": { + "id": "72a94fd8", + "outputId": "b024759c-3925-40c1-9c59-2f9dabee0248" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Hello, my name is Andrej and I've been training deep neural networks for a bit more than a decade. And in this lecture I'd like to show you what neural network training looks like under the hood. So in particular we are going to start with a blank Jupyter notebook and by the end of this lecture we will define and train a neural net and you'll get to see everything that goes on under the hood and exactly sort of how that works on an intuitive level. Now specifically what I would like to do is I w\"" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Returns a list of Documents, which can be easily viewed or parsed\n", + "docs[0].page_content[0:500]" + ] + }, + { + "cell_type": "markdown", + "id": "93be6b49", + "metadata": { + "id": "93be6b49" + }, + "source": [ + "### Building a chat app from YouTube video\n", + "\n", + "Given `Documents`, we can easily enable chat / question+answering." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1823f042", + "metadata": { + "id": "1823f042" + }, + "outputs": [], + "source": [ + "from langchain.chains import RetrievalQA\n", + "from langchain_community.vectorstores import FAISS\n", + "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7257cda1", + "metadata": { + "id": "7257cda1" + }, + "outputs": [], + "source": [ + "# Combine doc\n", + "combined_docs = [doc.page_content for doc in docs]\n", + "text = \" \".join(combined_docs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147c0c55", + "metadata": { + "id": "147c0c55" + }, + "outputs": [], + "source": [ + "# Split them\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=150)\n", + "splits = text_splitter.split_text(text)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3556703", + "metadata": { + "id": "f3556703" + }, + "outputs": [], + "source": [ + "# Build an index\n", + "embeddings = OpenAIEmbeddings()\n", + "vectordb = FAISS.from_texts(splits, embeddings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "beaa99db", + "metadata": { + "id": "beaa99db" + }, + "outputs": [], + "source": [ + "# Build a QA chain\n", + "qa_chain = RetrievalQA.from_chain_type(\n", + " llm=ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0),\n", + " chain_type=\"stuff\",\n", + " retriever=vectordb.as_retriever(),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2239a62", + "metadata": { + "id": "f2239a62", + "outputId": "b8de052d-cb76-44c5-bb0c-57e7398e89e6" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\"We need to zero out the gradient before backprop at each step because the backward pass accumulates gradients in the grad attribute of each parameter. If we don't reset the grad to zero before each backward pass, the gradients will accumulate and add up, leading to incorrect updates and slower convergence. By resetting the grad to zero before each backward pass, we ensure that the gradients are calculated correctly and that the optimization process works as intended.\"" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Ask a question!\n", + "query = \"Why do we need to zero out the gradient before backprop at each step?\"\n", + "qa_chain.run(query)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8d01098", + "metadata": { + "id": "a8d01098", + "outputId": "9d66d66e-fc7f-4ac9-b104-a8e45e962949" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'In the context of transformers, an encoder is a component that reads in a sequence of input tokens and generates a sequence of hidden representations. On the other hand, a decoder is a component that takes in a sequence of hidden representations and generates a sequence of output tokens. The main difference between the two is that the encoder is used to encode the input sequence into a fixed-length representation, while the decoder is used to decode the fixed-length representation into an output sequence. In machine translation, for example, the encoder reads in the source language sentence and generates a fixed-length representation, which is then used by the decoder to generate the target language sentence.'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query = \"What is the difference between an encoder and decoder?\"\n", + "qa_chain.run(query)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe1e77dd", + "metadata": { + "id": "fe1e77dd", + "outputId": "19479403-92c9-471e-8c93-5df4b17f007a" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'For any token, x is the input vector that contains the private information of that token, k and q are the key and query vectors respectively, which are produced by forwarding linear modules on x, and v is the vector that is calculated by propagating the same linear module on x again. The key vector represents what the token contains, and the query vector represents what the token is looking for. The vector v is the information that the token will communicate to other tokens if it finds them interesting, and it gets aggregated for the purposes of the self-attention mechanism.'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query = \"For any token, what are x, k, v, and q?\"\n", + "qa_chain.run(query)" ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" } - ], - "source": [ - "query = \"For any token, what are x, k, v, and q?\"\n", - "qa_chain.run(query)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "vscode": { + "interpreter": { + "hash": "97cc609b13305c559618ec78a438abc56230b9381f827f22d070313b9a1f3777" + } + }, + "colab": { + "provenance": [] + } }, - "vscode": { - "interpreter": { - "hash": "97cc609b13305c559618ec78a438abc56230b9381f827f22d070313b9a1f3777" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file From e4ceafa1c8f167ec437e492cbac9fe055451f2c5 Mon Sep 17 00:00:00 2001 From: ccurme Date: Mon, 13 Jan 2025 10:04:22 -0500 Subject: [PATCH 05/47] langchain[patch]: update extended tests for compatibility with langchain-openai==0.3 (#29174) --- libs/langchain/tests/unit_tests/chat_models/test_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/langchain/tests/unit_tests/chat_models/test_base.py b/libs/langchain/tests/unit_tests/chat_models/test_base.py index 853a0d29488af..670d85e9a7af5 100644 --- a/libs/langchain/tests/unit_tests/chat_models/test_base.py +++ b/libs/langchain/tests/unit_tests/chat_models/test_base.py @@ -107,14 +107,14 @@ def test_configurable() -> None: "disable_streaming": False, "disabled_params": None, "model_name": "gpt-4o", - "temperature": 0.7, + "temperature": None, "model_kwargs": {}, "openai_api_key": SecretStr("foo"), "openai_api_base": None, "openai_organization": None, "openai_proxy": None, "request_timeout": None, - "max_retries": 2, + "max_retries": None, "presence_penalty": None, "reasoning_effort": None, "frequency_penalty": None, @@ -123,7 +123,7 @@ def test_configurable() -> None: "top_logprobs": None, "logit_bias": None, "streaming": False, - "n": 1, + "n": None, "top_p": None, "max_tokens": None, "tiktoken_model_name": None, From 0e3115330d6725cf047c2e039fe62e4230714a0b Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 13 Jan 2025 18:11:47 +0300 Subject: [PATCH 06/47] Add additional_instructions on openai assistan runs create. (#29164) - **Description**: In the functions `_create_run` and `_acreate_run`, the parameters passed to the creation of `openai.resources.beta.threads.runs` were limited. Source: ``` def _create_run(self, input: dict) -> Any: params = { k: v for k, v in input.items() if k in ("instructions", "model", "tools", "run_metadata") } return self.client.beta.threads.runs.create( input["thread_id"], assistant_id=self.assistant_id, **params, ) ``` - OpenAI Documentation ([createRun](https://platform.openai.com/docs/api-reference/runs/createRun)) - Full list of parameters `openai.resources.beta.threads.runs` ([source code](https://github.com/openai/openai-python/blob/main/src/openai/resources/beta/threads/runs/runs.py#L91)) - **Issue:** Fix #17574 - [x] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. See contribution guidelines for more: https://python.langchain.com/docs/contributing/ Co-authored-by: ccurme --- .../langchain/agents/openai_assistant/base.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/libs/langchain/langchain/agents/openai_assistant/base.py b/libs/langchain/langchain/agents/openai_assistant/base.py index dc85fb03037ae..63457b63de2c7 100644 --- a/libs/langchain/langchain/agents/openai_assistant/base.py +++ b/libs/langchain/langchain/agents/openai_assistant/base.py @@ -408,7 +408,8 @@ async def ainvoke( message_metadata: Metadata to associate with a new message. thread_metadata: Metadata to associate with new thread. Only relevant when a new thread is created. - instructions: Additional run instructions. + instructions: Overrides the instructions of the assistant. + additional_instructions: Appends additional instructions. model: Override Assistant model for this run. tools: Override Assistant tools for this run. run_metadata: Metadata to associate with new run. @@ -507,7 +508,14 @@ def _create_run(self, input: dict) -> Any: params = { k: v for k, v in input.items() - if k in ("instructions", "model", "tools", "run_metadata") + if k + in ( + "instructions", + "model", + "tools", + "additional_instructions", + "run_metadata", + ) } return self.client.beta.threads.runs.create( input["thread_id"], @@ -637,7 +645,14 @@ async def _acreate_run(self, input: dict) -> Any: params = { k: v for k, v in input.items() - if k in ("instructions", "model", "tools", "run_metadata") + if k + in ( + "instructions", + "model", + "tools", + "additional_instructions", + "run_metadata", + ) } return await self.async_client.beta.threads.runs.create( input["thread_id"], From 689592f9bb58fe600def230e4837c7b78a9d2195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tymon=20=C5=BBarski?= <68420753+tymzar@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:22:14 +0100 Subject: [PATCH 07/47] community: Fix rank-llm import paths for new 0.20.3 version (#29154) # **PR title**: "community: Fix rank-llm import paths for new 0.20.3 version" - The "community" package is being modified to handle updated import paths for the new `rank-llm` version. --- ## Description This PR updates the import paths for the `rank-llm` package to account for changes introduced in version `0.20.3`. The changes ensure compatibility with both pre- and post-revamp versions of `rank-llm`, specifically version `0.12.8`. Conditional imports are introduced based on the detected version of `rank-llm` to handle different path structures for `VicunaReranker`, `ZephyrReranker`, and `SafeOpenai`. ## Issue RankLLMRerank usage throws an error when used GPT (not only) when rank-llm version is > 0.12.8 - #29156 ## Dependencies This change relies on the `packaging` and `pkg_resources` libraries to handle version checks. ## Twitter handle @tymzar --- .../document_compressors/rankllm_rerank.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/libs/community/langchain_community/document_compressors/rankllm_rerank.py b/libs/community/langchain_community/document_compressors/rankllm_rerank.py index bcf18652928e2..fb5a92ba00656 100644 --- a/libs/community/langchain_community/document_compressors/rankllm_rerank.py +++ b/libs/community/langchain_community/document_compressors/rankllm_rerank.py @@ -2,12 +2,14 @@ from copy import deepcopy from enum import Enum +from importlib.metadata import version from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence from langchain.retrievers.document_compressors.base import BaseDocumentCompressor from langchain_core.callbacks.manager import Callbacks from langchain_core.documents import Document from langchain_core.utils import get_from_dict_or_env +from packaging.version import Version from pydantic import ConfigDict, Field, PrivateAttr, model_validator if TYPE_CHECKING: @@ -49,6 +51,10 @@ def validate_environment(cls, values: Dict) -> Any: if not values.get("client"): client_name = values.get("model", "zephyr") + is_pre_rank_llm_revamp = Version(version=version("rank_llm")) <= Version( + "0.12.8" + ) + try: model_enum = ModelType(client_name.lower()) except ValueError: @@ -58,15 +64,29 @@ def validate_environment(cls, values: Dict) -> Any: try: if model_enum == ModelType.VICUNA: - from rank_llm.rerank.vicuna_reranker import VicunaReranker + if is_pre_rank_llm_revamp: + from rank_llm.rerank.vicuna_reranker import VicunaReranker + else: + from rank_llm.rerank.listwise.vicuna_reranker import ( + VicunaReranker, + ) values["client"] = VicunaReranker() elif model_enum == ModelType.ZEPHYR: - from rank_llm.rerank.zephyr_reranker import ZephyrReranker + if is_pre_rank_llm_revamp: + from rank_llm.rerank.zephyr_reranker import ZephyrReranker + else: + from rank_llm.rerank.listwise.zephyr_reranker import ( + ZephyrReranker, + ) values["client"] = ZephyrReranker() elif model_enum == ModelType.GPT: - from rank_llm.rerank.rank_gpt import SafeOpenai + if is_pre_rank_llm_revamp: + from rank_llm.rerank.rank_gpt import SafeOpenai + else: + from rank_llm.rerank.listwise.rank_gpt import SafeOpenai + from rank_llm.rerank.reranker import Reranker openai_api_key = get_from_dict_or_env( From e64bfb537fbb27f386c2aada3b99870aedf9874b Mon Sep 17 00:00:00 2001 From: Gabe Cornejo <64950833+gccornejo441@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:26:01 -0500 Subject: [PATCH 08/47] docs: Fix old link to Unstructured package in document_loader_markdown.ipynb (#29175) Fixed a broken link in `document_loader_markdown.ipynb` to point to the updated documentation page for the Unstructured package. Issue: N/A Dependencies: None --- docs/docs/how_to/document_loader_markdown.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/how_to/document_loader_markdown.ipynb b/docs/docs/how_to/document_loader_markdown.ipynb index 385d3f687aaf0..2a28b8c34a849 100644 --- a/docs/docs/how_to/document_loader_markdown.ipynb +++ b/docs/docs/how_to/document_loader_markdown.ipynb @@ -16,7 +16,7 @@ "- Basic usage;\n", "- Parsing of Markdown into elements such as titles, list items, and text.\n", "\n", - "LangChain implements an [UnstructuredMarkdownLoader](https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.markdown.UnstructuredMarkdownLoader.html) object which requires the [Unstructured](https://unstructured-io.github.io/unstructured/) package. First we install it:" + "LangChain implements an [UnstructuredMarkdownLoader](https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.markdown.UnstructuredMarkdownLoader.html) object which requires the [Unstructured](https://docs.unstructured.io/welcome/) package. First we install it:" ] }, { From 4c0217681a81859da6b3547c46c0bb6af780307c Mon Sep 17 00:00:00 2001 From: Zhengren Wang Date: Mon, 13 Jan 2025 23:35:34 +0800 Subject: [PATCH 09/47] cookbook: fix typo in cookbook/mongodb-langchain-cache-memory.ipynb (#29149) Description: fix "enviornment" into "environment". Issue: Typo Dependencies: None Twitter handle: zrwang01 --- cookbook/mongodb-langchain-cache-memory.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/mongodb-langchain-cache-memory.ipynb b/cookbook/mongodb-langchain-cache-memory.ipynb index 0a1da72e14fe7..4a1f10adb16be 100644 --- a/cookbook/mongodb-langchain-cache-memory.ipynb +++ b/cookbook/mongodb-langchain-cache-memory.ipynb @@ -156,7 +156,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Ensure you have an HF_TOKEN in your development enviornment:\n", + "# Ensure you have an HF_TOKEN in your development environment:\n", "# access tokens can be created or copied from the Hugging Face platform (https://huggingface.co/docs/hub/en/security-tokens)\n", "\n", "# Load MongoDB's embedded_movies dataset from Hugging Face\n", From 335ca3a606009fa7f10b7c43e4b55154668cb288 Mon Sep 17 00:00:00 2001 From: Nikhil Shahi Date: Mon, 13 Jan 2025 09:45:39 -0600 Subject: [PATCH 10/47] docs: add HyperbrowserLoader docs (#29143) ### Description This PR adds docs for the [langchain-hyperbrowser](https://pypi.org/project/langchain-hyperbrowser/) package. It includes a document loader that uses Hyperbrowser to scrape or crawl any urls and return formatted markdown or html content as well as relevant metadata. [Hyperbrowser](https://hyperbrowser.ai) is a platform for running and scaling headless browsers. It lets you launch and manage browser sessions at scale and provides easy to use solutions for any webscraping needs, such as scraping a single page or crawling an entire site. ### Issue None ### Dependencies None ### Twitter Handle `@hyperbrowser` --- .../document_loaders/hyperbrowser.ipynb | 221 ++++++++++++++++++ .../integrations/providers/hyperbrowser.mdx | 67 ++++++ docs/src/theme/FeatureTables.js | 7 + libs/packages.yml | 4 + 4 files changed, 299 insertions(+) create mode 100644 docs/docs/integrations/document_loaders/hyperbrowser.ipynb create mode 100644 docs/docs/integrations/providers/hyperbrowser.mdx diff --git a/docs/docs/integrations/document_loaders/hyperbrowser.ipynb b/docs/docs/integrations/document_loaders/hyperbrowser.ipynb new file mode 100644 index 0000000000000..723d92afe31fb --- /dev/null +++ b/docs/docs/integrations/document_loaders/hyperbrowser.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# HyperbrowserLoader" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Hyperbrowser](https://hyperbrowser.ai) is a platform for running and scaling headless browsers. It lets you launch and manage browser sessions at scale and provides easy to use solutions for any webscraping needs, such as scraping a single page or crawling an entire site.\n", + "\n", + "Key Features:\n", + "- Instant Scalability - Spin up hundreds of browser sessions in seconds without infrastructure headaches\n", + "- Simple Integration - Works seamlessly with popular tools like Puppeteer and Playwright\n", + "- Powerful APIs - Easy to use APIs for scraping/crawling any site, and much more\n", + "- Bypass Anti-Bot Measures - Built-in stealth mode, ad blocking, automatic CAPTCHA solving, and rotating proxies\n", + "\n", + "This notebook provides a quick overview for getting started with Hyperbrowser [document loader](https://python.langchain.com/docs/concepts/#document-loaders).\n", + "\n", + "For more information about Hyperbrowser, please visit the [Hyperbrowser website](https://hyperbrowser.ai) or if you want to check out the docs, you can visit the [Hyperbrowser docs](https://docs.hyperbrowser.ai).\n", + "\n", + "## Overview\n", + "### Integration details\n", + "\n", + "| Class | Package | Local | Serializable | JS support|\n", + "| :--- | :--- | :---: | :---: | :---: |\n", + "| HyperbrowserLoader | langchain-hyperbrowser | ❌ | ❌ | ❌ | \n", + "### Loader features\n", + "| Source | Document Lazy Loading | Native Async Support |\n", + "| :---: | :---: | :---: | \n", + "| HyperbrowserLoader | ✅ | ✅ | \n", + "\n", + "## Setup\n", + "\n", + "To access Hyperbrowser document loader you'll need to install the `langchain-hyperbrowser` integration package, and create a Hyperbrowser account and get an API key.\n", + "\n", + "### Credentials\n", + "\n", + "Head to [Hyperbrowser](https://app.hyperbrowser.ai/) to sign up and generate an API key. Once you've done this set the HYPERBROWSER_API_KEY environment variable:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Installation\n", + "\n", + "Install **langchain-hyperbrowser**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -qU langchain-hyperbrowser" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialization\n", + "\n", + "Now we can instantiate our model object and load documents:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_hyperbrowser import HyperbrowserLoader\n", + "\n", + "loader = HyperbrowserLoader(\n", + " urls=\"https://example.com\",\n", + " api_key=\"YOUR_API_KEY\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Document(metadata={'title': 'Example Domain', 'viewport': 'width=device-width, initial-scale=1', 'sourceURL': 'https://example.com'}, page_content='Example Domain\\n\\n# Example Domain\\n\\nThis domain is for use in illustrative examples in documents. You may use this\\ndomain in literature without prior coordination or asking for permission.\\n\\n[More information...](https://www.iana.org/domains/example)')" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docs = loader.load()\n", + "docs[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(docs[0].metadata)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lazy Load" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "page = []\n", + "for doc in loader.lazy_load():\n", + " page.append(doc)\n", + " if len(page) >= 10:\n", + " # do some paged operation, e.g.\n", + " # index.upsert(page)\n", + "\n", + " page = []" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced Usage\n", + "\n", + "You can specify the operation to be performed by the loader. The default operation is `scrape`. For `scrape`, you can provide a single URL or a list of URLs to be scraped. For `crawl`, you can only provide a single URL. The `crawl` operation will crawl the provided page and subpages and return a document for each page." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "loader = HyperbrowserLoader(\n", + " urls=\"https://hyperbrowser.ai\", api_key=\"YOUR_API_KEY\", operation=\"crawl\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Optional params for the loader can also be provided in the `params` argument. For more information on the supported params, visit https://docs.hyperbrowser.ai/reference/sdks/python/scrape#start-scrape-job-and-wait or https://docs.hyperbrowser.ai/reference/sdks/python/crawl#start-crawl-job-and-wait." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "loader = HyperbrowserLoader(\n", + " urls=\"https://example.com\",\n", + " api_key=\"YOUR_API_KEY\",\n", + " operation=\"scrape\",\n", + " params={\"scrape_options\": {\"include_tags\": [\"h1\", \"h2\", \"p\"]}},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## API reference\n", + "\n", + "- [GitHub](https://github.com/hyperbrowserai/langchain-hyperbrowser/)\n", + "- [PyPi](https://pypi.org/project/langchain-hyperbrowser/)\n", + "- [Hyperbrowser Docs](https://docs.hyperbrowser.ai/)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/docs/integrations/providers/hyperbrowser.mdx b/docs/docs/integrations/providers/hyperbrowser.mdx new file mode 100644 index 0000000000000..6efbd47a2eb0c --- /dev/null +++ b/docs/docs/integrations/providers/hyperbrowser.mdx @@ -0,0 +1,67 @@ +# Hyperbrowser + +> [Hyperbrowser](https://hyperbrowser.ai) is a platform for running and scaling headless browsers. It lets you launch and manage browser sessions at scale and provides easy to use solutions for any webscraping needs, such as scraping a single page or crawling an entire site. +> +> Key Features: +> +> - Instant Scalability - Spin up hundreds of browser sessions in seconds without infrastructure headaches +> - Simple Integration - Works seamlessly with popular tools like Puppeteer and Playwright +> - Powerful APIs - Easy to use APIs for scraping/crawling any site, and much more +> - Bypass Anti-Bot Measures - Built-in stealth mode, ad blocking, automatic CAPTCHA solving, and rotating proxies + +For more information about Hyperbrowser, please visit the [Hyperbrowser website](https://hyperbrowser.ai) or if you want to check out the docs, you can visit the [Hyperbrowser docs](https://docs.hyperbrowser.ai). + +## Installation and Setup + +To get started with `langchain-hyperbrowser`, you can install the package using pip: + +```bash +pip install langchain-hyperbrowser +``` + +And you should configure credentials by setting the following environment variables: + +`HYPERBROWSER_API_KEY=` + +Make sure to get your API Key from https://app.hyperbrowser.ai/ + +## Document Loader + +The `HyperbrowserLoader` class in `langchain-hyperbrowser` can easily be used to load content from any single page or multiple pages as well as crawl an entire site. +The content can be loaded as markdown or html. + +```python +from langchain_hyperbrowser import HyperbrowserLoader + +loader = HyperbrowserLoader(urls="https://example.com") +docs = loader.load() + +print(docs[0]) +``` + +## Advanced Usage + +You can specify the operation to be performed by the loader. The default operation is `scrape`. For `scrape`, you can provide a single URL or a list of URLs to be scraped. For `crawl`, you can only provide a single URL. The `crawl` operation will crawl the provided page and subpages and return a document for each page. + +```python +loader = HyperbrowserLoader( + urls="https://hyperbrowser.ai", api_key="YOUR_API_KEY", operation="crawl" +) +``` + +Optional params for the loader can also be provided in the `params` argument. For more information on the supported params, visit https://docs.hyperbrowser.ai/reference/sdks/python/scrape#start-scrape-job-and-wait or https://docs.hyperbrowser.ai/reference/sdks/python/crawl#start-crawl-job-and-wait. + +```python +loader = HyperbrowserLoader( + urls="https://example.com", + api_key="YOUR_API_KEY", + operation="scrape", + params={"scrape_options": {"include_tags": ["h1", "h2", "p"]}} +) +``` + +## Additional Resources + +- [Hyperbrowser Docs](https://docs.hyperbrowser.ai/) +- [GitHub](https://github.com/hyperbrowserai/langchain-hyperbrowser/) +- [PyPi](https://pypi.org/project/langchain-hyperbrowser/) diff --git a/docs/src/theme/FeatureTables.js b/docs/src/theme/FeatureTables.js index bebe1739e6e31..d56d5a80c1515 100644 --- a/docs/src/theme/FeatureTables.js +++ b/docs/src/theme/FeatureTables.js @@ -815,6 +815,13 @@ const FEATURE_TABLES = { source: "Uses Docling to load and parse web pages", api: "Package", apiLink: "https://python.langchain.com/docs/integrations/document_loaders/docling/" + }, + { + name: "Hyperbrowser", + link: "hyperbrowser", + source: "Platform for running and scaling headless browsers, can be used to scrape/crawl any site", + api: "API", + apiLink: "https://python.langchain.com/docs/integrations/document_loaders/hyperbrowser/" } ] }, diff --git a/libs/packages.yml b/libs/packages.yml index 7c8970e7ecc0d..e95f8c37c1b94 100644 --- a/libs/packages.yml +++ b/libs/packages.yml @@ -337,3 +337,7 @@ packages: path: . repo: AlwaysBluer/langchain-lindorm-integration downloads: 0 +- name: langchain-hyperbrowser + path: . + repo: hyperbrowserai/langchain-hyperbrowser + downloads: 0 From e156b372fb6da26deceffaff2f42aab1036da3b6 Mon Sep 17 00:00:00 2001 From: Christopher Varjas Date: Mon, 13 Jan 2025 11:00:02 -0500 Subject: [PATCH 11/47] langchain: support api key argument with OpenAI moderation chain (#29140) **Description:** Makes it possible to instantiate `OpenAIModerationChain` with an `openai_api_key` argument only and no `OPENAI_API_KEY` environment variable defined. **Issue:** https://github.com/langchain-ai/langchain/issues/25176 **Dependencies:** `openai` --------- Co-authored-by: ccurme --- libs/langchain/langchain/chains/moderation.py | 4 ++-- .../chains/openai_functions/test_openapi.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/libs/langchain/langchain/chains/moderation.py b/libs/langchain/langchain/chains/moderation.py index 9467b8894c12b..f72cae6cc8d7d 100644 --- a/libs/langchain/langchain/chains/moderation.py +++ b/libs/langchain/langchain/chains/moderation.py @@ -67,8 +67,8 @@ def validate_environment(cls, values: Dict) -> Any: if values["openai_pre_1_0"]: values["client"] = openai.Moderation else: - values["client"] = openai.OpenAI() - values["async_client"] = openai.AsyncOpenAI() + values["client"] = openai.OpenAI(api_key=openai_api_key) + values["async_client"] = openai.AsyncOpenAI(api_key=openai_api_key) except ImportError: raise ImportError( diff --git a/libs/langchain/tests/integration_tests/chains/openai_functions/test_openapi.py b/libs/langchain/tests/integration_tests/chains/openai_functions/test_openapi.py index d3c294cbd56ab..9db8b957698b1 100644 --- a/libs/langchain/tests/integration_tests/chains/openai_functions/test_openapi.py +++ b/libs/langchain/tests/integration_tests/chains/openai_functions/test_openapi.py @@ -2,6 +2,7 @@ import pytest +from langchain.chains import OpenAIModerationChain from langchain.chains.openai_functions.openapi import get_openapi_chain api_spec = { @@ -36,3 +37,13 @@ def test_openai_openapi_chain() -> None: chain = get_openapi_chain(json.dumps(api_spec), llm) output = chain.invoke({"query": "Fetch the top two posts."}) assert len(output["response"]) == 2 + + +@pytest.mark.requires("openai") +def test_openai_moderation_chain_instantiation() -> None: + """Test OpenAIModerationChain.""" + api_key = "foo" + + moderation = OpenAIModerationChain(openai_api_key=api_key) + + assert isinstance(moderation, OpenAIModerationChain) From 1bf6576709b8c4a4014d5f62cf955c19df1fdf02 Mon Sep 17 00:00:00 2001 From: ccurme Date: Mon, 13 Jan 2025 13:28:18 -0500 Subject: [PATCH 12/47] cli[patch]: fix anchor links in templates (#29178) These are outdated and can break docs builds. --- libs/cli/langchain_cli/integration_template/docs/chat.ipynb | 2 +- .../integration_template/docs/document_loaders.ipynb | 2 +- .../langchain_cli/integration_template/docs/retrievers.ipynb | 2 +- .../langchain_cli/integration_template/docs/toolkits.ipynb | 2 +- libs/cli/langchain_cli/integration_template/docs/tools.ipynb | 4 ++-- .../integration_template/docs/vectorstores.ipynb | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/cli/langchain_cli/integration_template/docs/chat.ipynb b/libs/cli/langchain_cli/integration_template/docs/chat.ipynb index 95ac5d9c5d3a8..c0dbef513554b 100644 --- a/libs/cli/langchain_cli/integration_template/docs/chat.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/chat.ipynb @@ -19,7 +19,7 @@ "\n", "- TODO: Make sure API reference link is correct.\n", "\n", - "This will help you getting started with __ModuleName__ [chat models](/docs/concepts/#chat-models). For detailed documentation of all Chat__ModuleName__ features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/chat_models/__module_name__.chat_models.Chat__ModuleName__.html).\n", + "This will help you getting started with __ModuleName__ [chat models](/docs/concepts/chat_models). For detailed documentation of all Chat__ModuleName__ features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/chat_models/__module_name__.chat_models.Chat__ModuleName__.html).\n", "\n", "- TODO: Add any other relevant links, like information about models, prices, context windows, etc. See https://python.langchain.com/docs/integrations/chat/openai/ for an example.\n", "\n", diff --git a/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb b/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb index e457102b5cf91..67ac60d249f92 100644 --- a/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb @@ -17,7 +17,7 @@ "\n", "- TODO: Make sure API reference link is correct.\n", "\n", - "This notebook provides a quick overview for getting started with __ModuleName__ [document loader](https://python.langchain.com/docs/concepts/#document-loaders). For detailed documentation of all __ModuleName__Loader features and configurations head to the [API reference](https://python.langchain.com/v0.2/api_reference/community/document_loaders/langchain_community.document_loaders.__module_name___loader.__ModuleName__Loader.html).\n", + "This notebook provides a quick overview for getting started with __ModuleName__ [document loader](https://python.langchain.com/docs/concepts/document_loaders). For detailed documentation of all __ModuleName__Loader features and configurations head to the [API reference](https://python.langchain.com/v0.2/api_reference/community/document_loaders/langchain_community.document_loaders.__module_name___loader.__ModuleName__Loader.html).\n", "\n", "- TODO: Add any other relevant links, like information about underlying API, etc.\n", "\n", diff --git a/libs/cli/langchain_cli/integration_template/docs/retrievers.ipynb b/libs/cli/langchain_cli/integration_template/docs/retrievers.ipynb index 78f779301b11c..20d36e4f09f65 100644 --- a/libs/cli/langchain_cli/integration_template/docs/retrievers.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/retrievers.ipynb @@ -19,7 +19,7 @@ "\n", "- TODO: Make sure API reference link is correct.\n", "\n", - "This will help you getting started with the __ModuleName__ [retriever](/docs/concepts/#retrievers). For detailed documentation of all __ModuleName__Retriever features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/retrievers/__module_name__.retrievers.__ModuleName__.__ModuleName__Retriever.html).\n", + "This will help you getting started with the __ModuleName__ [retriever](/docs/concepts/retrievers). For detailed documentation of all __ModuleName__Retriever features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/retrievers/__module_name__.retrievers.__ModuleName__.__ModuleName__Retriever.html).\n", "\n", "### Integration details\n", "\n", diff --git a/libs/cli/langchain_cli/integration_template/docs/toolkits.ipynb b/libs/cli/langchain_cli/integration_template/docs/toolkits.ipynb index da948c899a093..23fbd2903cd27 100644 --- a/libs/cli/langchain_cli/integration_template/docs/toolkits.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/toolkits.ipynb @@ -19,7 +19,7 @@ "\n", "- TODO: Make sure API reference link is correct.\n", "\n", - "This will help you getting started with the __ModuleName__ [toolkit](/docs/concepts/#toolkits). For detailed documentation of all __ModuleName__Toolkit features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/agent_toolkits/__module_name__.agent_toolkits.__ModuleName__.toolkit.__ModuleName__Toolkit.html).\n", + "This will help you getting started with the __ModuleName__ [toolkit](/docs/concepts/tools/#toolkits). For detailed documentation of all __ModuleName__Toolkit features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/agent_toolkits/__module_name__.agent_toolkits.__ModuleName__.toolkit.__ModuleName__Toolkit.html).\n", "\n", "## Setup\n", "\n", diff --git a/libs/cli/langchain_cli/integration_template/docs/tools.ipynb b/libs/cli/langchain_cli/integration_template/docs/tools.ipynb index f1e02ca844f3f..7ca2ac5378d56 100644 --- a/libs/cli/langchain_cli/integration_template/docs/tools.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/tools.ipynb @@ -132,7 +132,7 @@ "source": [ "## Invocation\n", "\n", - "### [Invoke directly with args](/docs/concepts/#invoke-with-just-the-arguments)\n", + "### [Invoke directly with args](/docs/concepts/tools/#use-the-tool-directly)\n", "\n", "- TODO: Describe what the tool args are, fill them in, run cell" ] @@ -152,7 +152,7 @@ "id": "d6e73897", "metadata": {}, "source": [ - "### [Invoke with ToolCall](/docs/concepts/#invoke-with-toolcall)\n", + "### [Invoke with ToolCall](/docs/concepts/tool_calling/#tool-execution)\n", "\n", "We can also invoke the tool with a model-generated ToolCall, in which case a ToolMessage will be returned:\n", "\n", diff --git a/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb b/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb index 2ae2da87291bc..bbf0b85a2fafa 100644 --- a/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb @@ -294,7 +294,7 @@ "\n", "- [Tutorials](/docs/tutorials/)\n", "- [How-to: Question and answer with RAG](https://python.langchain.com/docs/how_to/#qa-with-rag)\n", - "- [Retrieval conceptual docs](https://python.langchain.com/docs/concepts/#retrieval)" + "- [Retrieval conceptual docs](https://python.langchain.com/docs/concepts/retrieval/)" ] }, { From cdf3a17e55bd594341c390051dc20c5e5a74b966 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Mon, 13 Jan 2025 13:25:00 -0800 Subject: [PATCH 13/47] docs: fix httpx conflicts with overrides in docs build (#29180) --- .github/workflows/api_doc_build.yml | 2 +- docs/Makefile | 2 +- docs/vercel_overrides.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 docs/vercel_overrides.txt diff --git a/.github/workflows/api_doc_build.yml b/.github/workflows/api_doc_build.yml index 28ad2d742367a..42007cb1d9a2d 100644 --- a/.github/workflows/api_doc_build.yml +++ b/.github/workflows/api_doc_build.yml @@ -72,7 +72,7 @@ jobs: - name: Install dependencies working-directory: langchain run: | - python -m uv pip install $(ls ./libs/partners | xargs -I {} echo "./libs/partners/{}") + python -m uv pip install $(ls ./libs/partners | xargs -I {} echo "./libs/partners/{}") --overrides ./docs/vercel_overrides.txt python -m uv pip install libs/core libs/langchain libs/text-splitters libs/community libs/experimental libs/standard-tests python -m uv pip install -r docs/api_reference/requirements.txt diff --git a/docs/Makefile b/docs/Makefile index 5df104397aade..43fb70bb105ed 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -27,7 +27,7 @@ install-py-deps: $(PYTHON) -m pip install -q --upgrade pip $(PYTHON) -m pip install -q --upgrade uv $(PYTHON) -m uv pip install -q --pre -r vercel_requirements.txt - $(PYTHON) -m uv pip install -q --pre $$($(PYTHON) scripts/partner_deps_list.py) + $(PYTHON) -m uv pip install -q --pre $$($(PYTHON) scripts/partner_deps_list.py) --overrides vercel_overrides.txt generate-files: mkdir -p $(INTERMEDIATE_DIR) diff --git a/docs/vercel_overrides.txt b/docs/vercel_overrides.txt new file mode 100644 index 0000000000000..79228389fce46 --- /dev/null +++ b/docs/vercel_overrides.txt @@ -0,0 +1 @@ +httpx \ No newline at end of file From c55af44711ba9180ce8a51a55a385f31023341b5 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Mon, 13 Jan 2025 15:32:40 -0800 Subject: [PATCH 14/47] anthropic: pydantic mypy plugin (#29144) --- libs/partners/anthropic/pyproject.toml | 1 + .../integration_tests/test_chat_models.py | 26 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/libs/partners/anthropic/pyproject.toml b/libs/partners/anthropic/pyproject.toml index abe04e73dae8d..645f6b5d604fa 100644 --- a/libs/partners/anthropic/pyproject.toml +++ b/libs/partners/anthropic/pyproject.toml @@ -13,6 +13,7 @@ license = "MIT" [tool.mypy] disallow_untyped_defs = "True" +plugins = ['pydantic.mypy'] [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/anthropic" diff --git a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py index 2fdf71845636f..9f2eaba45549d 100644 --- a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py +++ b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py @@ -153,7 +153,7 @@ async def test_abatch_tags() -> None: async def test_async_tool_use() -> None: - llm = ChatAnthropic( # type: ignore[call-arg] + llm = ChatAnthropic( model=MODEL_NAME, ) @@ -249,7 +249,7 @@ def test_system_invoke() -> None: def test_anthropic_call() -> None: """Test valid call to anthropic.""" - chat = ChatAnthropic(model=MODEL_NAME) # type: ignore[call-arg] + chat = ChatAnthropic(model=MODEL_NAME) message = HumanMessage(content="Hello") response = chat.invoke([message]) assert isinstance(response, AIMessage) @@ -258,7 +258,7 @@ def test_anthropic_call() -> None: def test_anthropic_generate() -> None: """Test generate method of anthropic.""" - chat = ChatAnthropic(model=MODEL_NAME) # type: ignore[call-arg] + chat = ChatAnthropic(model=MODEL_NAME) chat_messages: List[List[BaseMessage]] = [ [HumanMessage(content="How many toes do dogs have?")] ] @@ -274,7 +274,7 @@ def test_anthropic_generate() -> None: def test_anthropic_streaming() -> None: """Test streaming tokens from anthropic.""" - chat = ChatAnthropic(model=MODEL_NAME) # type: ignore[call-arg] + chat = ChatAnthropic(model=MODEL_NAME) message = HumanMessage(content="Hello") response = chat.stream([message]) for token in response: @@ -286,7 +286,7 @@ def test_anthropic_streaming_callback() -> None: """Test that streaming correctly invokes on_llm_new_token callback.""" callback_handler = FakeCallbackHandler() callback_manager = CallbackManager([callback_handler]) - chat = ChatAnthropic( # type: ignore[call-arg] + chat = ChatAnthropic( model=MODEL_NAME, callback_manager=callback_manager, verbose=True, @@ -302,7 +302,7 @@ async def test_anthropic_async_streaming_callback() -> None: """Test that streaming correctly invokes on_llm_new_token callback.""" callback_handler = FakeCallbackHandler() callback_manager = CallbackManager([callback_handler]) - chat = ChatAnthropic( # type: ignore[call-arg] + chat = ChatAnthropic( model=MODEL_NAME, callback_manager=callback_manager, verbose=True, @@ -318,7 +318,7 @@ async def test_anthropic_async_streaming_callback() -> None: def test_anthropic_multimodal() -> None: """Test that multimodal inputs are handled correctly.""" - chat = ChatAnthropic(model=MODEL_NAME) # type: ignore[call-arg] + chat = ChatAnthropic(model=MODEL_NAME) messages: list[BaseMessage] = [ HumanMessage( content=[ @@ -369,7 +369,7 @@ async def test_astreaming() -> None: def test_tool_use() -> None: - llm = ChatAnthropic(model=MODEL_NAME) # type: ignore[call-arg] + llm = ChatAnthropic(model=MODEL_NAME) llm_with_tools = llm.bind_tools( [ { @@ -452,7 +452,7 @@ def type_letter(letter: str) -> str: """Type the given letter.""" return "OK" - model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0).bind_tools( # type: ignore[call-arg] + model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0).bind_tools( [type_letter] ) @@ -490,7 +490,7 @@ def type_letter(letter: str) -> str: def test_with_structured_output() -> None: - llm = ChatAnthropic( # type: ignore[call-arg] + llm = ChatAnthropic( model="claude-3-opus-20240229", ) @@ -510,7 +510,7 @@ def test_with_structured_output() -> None: def test_get_num_tokens_from_messages() -> None: - llm = ChatAnthropic(model="claude-3-5-sonnet-20241022") # type: ignore[call-arg] + llm = ChatAnthropic(model="claude-3-5-sonnet-20241022") # Test simple case messages = [ @@ -571,7 +571,7 @@ class GetWeather(BaseModel): @pytest.mark.parametrize("tool_choice", ["GetWeather", "auto", "any"]) def test_anthropic_bind_tools_tool_choice(tool_choice: str) -> None: - chat_model = ChatAnthropic( # type: ignore[call-arg] + chat_model = ChatAnthropic( model=MODEL_NAME, ) chat_model_with_tools = chat_model.bind_tools([GetWeather], tool_choice=tool_choice) @@ -583,7 +583,7 @@ def test_pdf_document_input() -> None: url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" data = b64encode(requests.get(url).content).decode() - result = ChatAnthropic(model_name=MODEL_NAME).invoke( # type: ignore[call-arg] + result = ChatAnthropic(model=MODEL_NAME).invoke( [ HumanMessage( [ From d9b856abadef0e7e7338a82f0b2e1239ce3fbd61 Mon Sep 17 00:00:00 2001 From: Michael Chin Date: Tue, 14 Jan 2025 07:23:34 -0800 Subject: [PATCH 15/47] community: Deprecate Amazon Neptune resources in langchain-community (#29191) Related: https://github.com/langchain-ai/langchain-aws/pull/322 The legacy `NeptuneOpenCypherQAChain` and `NeptuneSparqlQAChain` classes are being replaced by the new LCEL format chains `create_neptune_opencypher_qa_chain` and `create_neptune_sparql_qa_chain`, respectively, in the `langchain_aws` package. This PR adds deprecation warnings to all Neptune classes and functions that have been migrated to `langchain_aws`. All relevant documentation has also been updated to replace `langchain_community` usage with the new `langchain_aws` implementations. --------- Co-authored-by: Chester Curme --- .../graphs/amazon_neptune_open_cypher.ipynb | 46 +++++------ .../graphs/amazon_neptune_sparql.ipynb | 82 ++++++++++++------- docs/docs/integrations/providers/aws.mdx | 21 +++-- .../chains/graph_qa/neptune_cypher.py | 6 ++ .../chains/graph_qa/neptune_sparql.py | 6 ++ .../graphs/neptune_graph.py | 12 +++ .../graphs/neptune_rdf_graph.py | 6 ++ 7 files changed, 120 insertions(+), 59 deletions(-) diff --git a/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb b/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb index d7a29746face9..e25719a1b63c2 100644 --- a/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb +++ b/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb @@ -15,13 +15,14 @@ ">[openCypher](https://opencypher.org/) is an open-source implementation of Cypher.# Neptune Open Cypher QA Chain\n", "This QA chain queries Amazon Neptune using openCypher and returns human readable response\n", "\n", - "LangChain supports both [Neptune Database](https://docs.aws.amazon.com/neptune/latest/userguide/intro.html) and [Neptune Analytics](https://docs.aws.amazon.com/neptune-analytics/latest/userguide/what-is-neptune-analytics.html) with `NeptuneOpenCypherQAChain` \n", - "\n", + "LangChain supports both [Neptune Database](https://docs.aws.amazon.com/neptune/latest/userguide/intro.html) and [Neptune Analytics](https://docs.aws.amazon.com/neptune-analytics/latest/userguide/what-is-neptune-analytics.html) with `create_neptune_opencypher_qa_chain`.\n", "\n", "Neptune Database is a serverless graph database designed for optimal scalability and availability. It provides a solution for graph database workloads that need to scale to 100,000 queries per second, Multi-AZ high availability, and multi-Region deployments. You can use Neptune Database for social networking, fraud alerting, and Customer 360 applications.\n", "\n", "Neptune Analytics is an analytics database engine that can quickly analyze large amounts of graph data in memory to get insights and find trends. Neptune Analytics is a solution for quickly analyzing existing graph databases or graph datasets stored in a data lake. It uses popular graph analytic algorithms and low-latency analytic queries.\n", "\n", + "\n", + "\n", "## Using Neptune Database" ] }, @@ -31,7 +32,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain_community.graphs import NeptuneGraph\n", + "from langchain_aws.graphs import NeptuneGraph\n", "\n", "host = \"\"\n", "port = 8182\n", @@ -53,7 +54,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain_community.graphs import NeptuneAnalyticsGraph\n", + "from langchain_aws.graphs import NeptuneAnalyticsGraph\n", "\n", "graph = NeptuneAnalyticsGraph(graph_identifier=\"\")" ] @@ -62,36 +63,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Using NeptuneOpenCypherQAChain\n", + "## Using the Neptune openCypher QA Chain\n", "\n", - "This QA chain queries Neptune graph database using openCypher and returns human readable response." + "This QA chain queries the Neptune graph database using openCypher and returns a human-readable response." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'The Austin airport has 98 outgoing routes.'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "from langchain.chains import NeptuneOpenCypherQAChain\n", - "from langchain_openai import ChatOpenAI\n", + "from langchain_aws import ChatBedrockConverse\n", + "from langchain_aws.chains import create_neptune_opencypher_qa_chain\n", "\n", - "llm = ChatOpenAI(temperature=0, model=\"gpt-4\")\n", + "MODEL_ID = \"anthropic.claude-3-5-sonnet-20241022-v2:0\"\n", + "llm = ChatBedrockConverse(\n", + " model=MODEL_ID,\n", + " temperature=0,\n", + ")\n", "\n", - "chain = NeptuneOpenCypherQAChain.from_llm(llm=llm, graph=graph)\n", + "chain = create_neptune_opencypher_qa_chain(\n", + " llm=llm,\n", + " graph=graph,\n", + ")\n", "\n", - "chain.invoke(\"how many outgoing routes does the Austin airport have?\")" + "result = chain.invoke(\n", + " {\"query\": \"How many outgoing routes does the Austin airport have?\"}\n", + ")\n", + "print(result[\"result\"].content)" ] } ], diff --git a/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb b/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb index 1e1ddf199aead..3fb1811239895 100644 --- a/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb +++ b/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb @@ -15,7 +15,7 @@ "\n", "\n", "This example uses a `NeptuneRdfGraph` class that connects with the Neptune database and loads its schema. \n", - "The `NeptuneSparqlQAChain` is used to connect the graph and LLM to ask natural language questions.\n", + "The `create_neptune_sparql_qa_chain` is used to connect the graph and LLM to ask natural language questions.\n", "\n", "This notebook demonstrates an example using organizational data.\n", "\n", @@ -60,6 +60,11 @@ "STAGE_BUCKET = \"\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "" + }, { "cell_type": "code", "execution_count": null, @@ -118,7 +123,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install --upgrade --quiet langchain langchain-community langchain-aws" + "!pip install --upgrade --quiet langchain-aws" ] }, { @@ -238,48 +243,37 @@ "\"\"\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "### Create the Neptune Database RDF Graph" + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import boto3\n", - "from langchain_aws import ChatBedrock\n", - "from langchain_community.chains.graph_qa.neptune_sparql import NeptuneSparqlQAChain\n", - "from langchain_community.graphs import NeptuneRdfGraph\n", + "from langchain_aws.graphs import NeptuneRdfGraph\n", "\n", "host = \"\"\n", "port = 8182 # change if different\n", "region = \"us-east-1\" # change if different\n", "graph = NeptuneRdfGraph(host=host, port=port, use_iam_auth=True, region_name=region)\n", "\n", - "# Optionally change the schema\n", + "# Optionally, change the schema\n", "# elems = graph.get_schema_elements\n", "# change elems ...\n", - "# graph.load_schema(elems)\n", - "\n", - "MODEL_ID = \"anthropic.claude-v2\"\n", - "bedrock_client = boto3.client(\"bedrock-runtime\")\n", - "llm = ChatBedrock(model_id=MODEL_ID, client=bedrock_client)\n", - "\n", - "chain = NeptuneSparqlQAChain.from_llm(\n", - " llm=llm,\n", - " graph=graph,\n", - " examples=EXAMPLES,\n", - " verbose=True,\n", - " top_K=10,\n", - " return_intermediate_steps=True,\n", - " return_direct=False,\n", - ")" + "# graph.load_schema(elems)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Ask questions\n", - "Depends on the data we ingested above" + "## Using the Neptune SPARQL QA Chain\n", + "\n", + "This QA chain queries the Neptune graph database using SPARQL and returns a human-readable response." ] }, { @@ -288,7 +282,31 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\"\"\"How many organizations are in the graph\"\"\")" + "from langchain_aws import ChatBedrockConverse\n", + "from langchain_aws.chains import create_neptune_sparql_qa_chain\n", + "\n", + "MODEL_ID = \"anthropic.claude-3-5-sonnet-20241022-v2:0\"\n", + "llm = ChatBedrockConverse(\n", + " model_id=MODEL_ID,\n", + " temperature=0,\n", + ")\n", + "\n", + "chain = create_neptune_sparql_qa_chain(\n", + " llm=llm,\n", + " graph=graph,\n", + " examples=EXAMPLES,\n", + ")\n", + "\n", + "result = chain.invoke({\"query\": \"How many organizations are in the graph?\"})\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extra questions\n", + "Here are a few more prompts to try on the graph data that was ingested.\n" ] }, { @@ -297,7 +315,7 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\"\"\"Are there any mergers or acquisitions\"\"\")" + "chain.invoke({\"query\": \"Are there any mergers or acquisitions?\"})" ] }, { @@ -306,7 +324,7 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\"\"\"Find organizations\"\"\")" + "chain.invoke({\"query\": \"Find organizations.\"})" ] }, { @@ -315,7 +333,7 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\"\"\"Find sites of MegaSystems or MegaFinancial\"\"\")" + "chain.invoke({\"query\": \"Find sites of MegaSystems or MegaFinancial.\"})" ] }, { @@ -324,7 +342,7 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\"\"\"Find a member who is manager of one or more members.\"\"\")" + "chain.invoke({\"query\": \"Find a member who is a manager of one or more members.\"})" ] }, { @@ -333,7 +351,7 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\"\"\"Find five members and who their manager is.\"\"\")" + "chain.invoke({\"query\": \"Find five members and their managers.\"})" ] }, { @@ -343,7 +361,9 @@ "outputs": [], "source": [ "chain.invoke(\n", - " \"\"\"Find org units or suborganizations of The Mega Group. What are the sites of those units?\"\"\"\n", + " {\n", + " \"query\": \"Find org units or suborganizations of The Mega Group. What are the sites of those units?\"\n", + " }\n", ")" ] } diff --git a/docs/docs/integrations/providers/aws.mdx b/docs/docs/integrations/providers/aws.mdx index 5b46b2e6b5836..b29f802105472 100755 --- a/docs/docs/integrations/providers/aws.mdx +++ b/docs/docs/integrations/providers/aws.mdx @@ -310,14 +310,25 @@ from langchain_community.chat_message_histories import DynamoDBChatMessageHistor ## Graphs +### Amazon Neptune + +>[Amazon Neptune](https://aws.amazon.com/neptune/) +> is a high-performance graph analytics and serverless database for superior scalability and availability. + +For the Cypher and SPARQL integrations below, we need to install the `langchain-aws` library. + +```bash +pip install langchain-aws +``` + ### Amazon Neptune with Cypher See a [usage example](/docs/integrations/graphs/amazon_neptune_open_cypher). ```python -from langchain_community.graphs import NeptuneGraph -from langchain_community.graphs import NeptuneAnalyticsGraph -from langchain_community.chains.graph_qa.neptune_cypher import NeptuneOpenCypherQAChain +from langchain_aws.graphs import NeptuneGraph +from langchain_aws.graphs import NeptuneAnalyticsGraph +from langchain_aws.chains import create_neptune_opencypher_qa_chain ``` ### Amazon Neptune with SPARQL @@ -325,8 +336,8 @@ from langchain_community.chains.graph_qa.neptune_cypher import NeptuneOpenCypher See a [usage example](/docs/integrations/graphs/amazon_neptune_sparql). ```python -from langchain_community.graphs import NeptuneRdfGraph -from langchain_community.chains.graph_qa.neptune_sparql import NeptuneSparqlQAChain +from langchain_aws.graphs import NeptuneRdfGraph +from langchain_aws.chains import create_neptune_sparql_qa_chain ``` diff --git a/libs/community/langchain_community/chains/graph_qa/neptune_cypher.py b/libs/community/langchain_community/chains/graph_qa/neptune_cypher.py index c17053b3d09b9..e0f21ff477b47 100644 --- a/libs/community/langchain_community/chains/graph_qa/neptune_cypher.py +++ b/libs/community/langchain_community/chains/graph_qa/neptune_cypher.py @@ -6,6 +6,7 @@ from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.prompt_selector import ConditionalPromptSelector +from langchain_core._api.deprecation import deprecated from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts.base import BasePromptTemplate @@ -82,6 +83,11 @@ def use_simple_prompt(llm: BaseLanguageModel) -> bool: ) +@deprecated( + since="0.3.15", + removal="1.0", + alternative_import="langchain_aws.create_neptune_opencypher_qa_chain", +) class NeptuneOpenCypherQAChain(Chain): """Chain for question-answering against a Neptune graph by generating openCypher statements. diff --git a/libs/community/langchain_community/chains/graph_qa/neptune_sparql.py b/libs/community/langchain_community/chains/graph_qa/neptune_sparql.py index f2341be8eda75..9155fae090e27 100644 --- a/libs/community/langchain_community/chains/graph_qa/neptune_sparql.py +++ b/libs/community/langchain_community/chains/graph_qa/neptune_sparql.py @@ -8,6 +8,7 @@ from langchain.chains.base import Chain from langchain.chains.llm import LLMChain +from langchain_core._api.deprecation import deprecated from langchain_core.callbacks.manager import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts.base import BasePromptTemplate @@ -75,6 +76,11 @@ def extract_sparql(query: str) -> str: return query +@deprecated( + since="0.3.15", + removal="1.0", + alternative_import="langchain_aws.create_neptune_sparql_qa_chain", +) class NeptuneSparqlQAChain(Chain): """Chain for question-answering against a Neptune graph by generating SPARQL statements. diff --git a/libs/community/langchain_community/graphs/neptune_graph.py b/libs/community/langchain_community/graphs/neptune_graph.py index dd684cf4a640d..d71fb07e38528 100644 --- a/libs/community/langchain_community/graphs/neptune_graph.py +++ b/libs/community/langchain_community/graphs/neptune_graph.py @@ -2,6 +2,8 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Tuple, Union +from langchain_core._api.deprecation import deprecated + class NeptuneQueryException(Exception): """Exception for the Neptune queries.""" @@ -139,6 +141,11 @@ def _refresh_schema(self) -> None: """ +@deprecated( + since="0.3.15", + removal="1.0", + alternative_import="langchain_aws.NeptuneAnalyticsGraph", +) class NeptuneAnalyticsGraph(BaseNeptuneGraph): """Neptune Analytics wrapper for graph operations. @@ -269,6 +276,11 @@ def _get_summary(self) -> Dict: return summary +@deprecated( + since="0.3.15", + removal="1.0", + alternative_import="langchain_aws.NeptuneGraph", +) class NeptuneGraph(BaseNeptuneGraph): """Neptune wrapper for graph operations. diff --git a/libs/community/langchain_community/graphs/neptune_rdf_graph.py b/libs/community/langchain_community/graphs/neptune_rdf_graph.py index 7ec42d8a3edb7..f560768a15ccc 100644 --- a/libs/community/langchain_community/graphs/neptune_rdf_graph.py +++ b/libs/community/langchain_community/graphs/neptune_rdf_graph.py @@ -3,6 +3,7 @@ from typing import Any, Dict, Optional, Sequence import requests +from langchain_core._api.deprecation import deprecated # Query to find OWL datatype properties DTPROP_QUERY = """ @@ -28,6 +29,11 @@ } +@deprecated( + since="0.3.15", + removal="1.0", + alternative_import="langchain_aws.NeptuneRdfGraph", +) class NeptuneRdfGraph: """Neptune wrapper for RDF graph operations. From 4ab04ad6be0f8f51d22a7df759d7719781fa22f5 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:55:16 -0800 Subject: [PATCH 16/47] docs: oai api ref nit (#29210) --- libs/partners/openai/langchain_openai/chat_models/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 3e77e6463391a..42b2501cbbfa6 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -534,7 +534,7 @@ class BaseChatOpenAI(BaseChatModel): used, or it's a list of disabled values for the parameter. For example, older models may not support the 'parallel_tool_calls' parameter at - all, in which case ``disabled_params={"parallel_tool_calls: None}`` can ben passed + all, in which case ``disabled_params={"parallel_tool_calls": None}`` can ben passed in. If a parameter is disabled then it will not be used by default in any methods, e.g. From efadad6067095a2a9afd4c3063bf777ca076584a Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 14 Jan 2025 20:27:52 +0200 Subject: [PATCH 17/47] Add Link to FalkorDB Memory example (#29204) - **Description:** Add Link to FalkorDB Memory example --- docs/docs/integrations/providers/falkordb.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/integrations/providers/falkordb.mdx b/docs/docs/integrations/providers/falkordb.mdx index 66afc5522e76c..4cd2f9cdecb53 100644 --- a/docs/docs/integrations/providers/falkordb.mdx +++ b/docs/docs/integrations/providers/falkordb.mdx @@ -24,3 +24,11 @@ See a [usage example](/docs/integrations/graphs/falkordb). ```python from langchain_community.chains.graph_qa.falkordb import FalkorDBQAChain ``` + +## Memory + +See a [usage example](/docs/integrations/memory/falkordb_chat_message_history). + +```python +from langchain_falkordb import FalkorDBChatMessageHistory +``` From 76172511fd1739016895413f6a656e3ffd3c8a32 Mon Sep 17 00:00:00 2001 From: pm390 <56439961+pm390@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:53:37 +0100 Subject: [PATCH 18/47] community: Additional parameters for OpenAIAssistantV2Runnable (#29207) **Description:** Added Additional parameters that could be useful for usage of OpenAIAssistantV2Runnable. This change is thought to allow langchain users to set parameters that cannot be set using assistants UI (max_completion_tokens,max_prompt_tokens,parallel_tool_calls) and parameters that could be useful for experimenting like top_p and temperature. This PR originated from the need of using parallel_tool_calls in langchain, this parameter is very important in openAI assistants because without this parameter set to False strict mode is not respected by OpenAI Assistants (https://platform.openai.com/docs/guides/function-calling#parallel-function-calling). > Note: Currently, if the model calls multiple functions in one turn then strict mode will be disabled for those calls. **Issue:** None **Dependencies:** openai --- .../langchain/agents/openai_assistant/base.py | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/libs/langchain/langchain/agents/openai_assistant/base.py b/libs/langchain/langchain/agents/openai_assistant/base.py index 63457b63de2c7..148cfc85ab5a3 100644 --- a/libs/langchain/langchain/agents/openai_assistant/base.py +++ b/libs/langchain/langchain/agents/openai_assistant/base.py @@ -293,6 +293,12 @@ def invoke( instructions: Additional run instructions. model: Override Assistant model for this run. tools: Override Assistant tools for this run. + parallel_tool_calls: Allow Assistant to set parallel_tool_calls + for this run. + top_p: Override Assistant top_p for this run. + temperature: Override Assistant temperature for this run. + max_completion_tokens: Allow setting max_completion_tokens for this run. + max_prompt_tokens: Allow setting max_prompt_tokens for this run. run_metadata: Metadata to associate with new run. config: Runnable config. Defaults to None. @@ -412,6 +418,12 @@ async def ainvoke( additional_instructions: Appends additional instructions. model: Override Assistant model for this run. tools: Override Assistant tools for this run. + parallel_tool_calls: Allow Assistant to set parallel_tool_calls + for this run. + top_p: Override Assistant top_p for this run. + temperature: Override Assistant temperature for this run. + max_completion_tokens: Allow setting max_completion_tokens for this run. + max_prompt_tokens: Allow setting max_prompt_tokens for this run. run_metadata: Metadata to associate with new run. config: Runnable config. Defaults to None. kwargs: Additional arguments. @@ -514,6 +526,11 @@ def _create_run(self, input: dict) -> Any: "model", "tools", "additional_instructions", + "parallel_tool_calls", + "top_p", + "temperature", + "max_completion_tokens", + "max_prompt_tokens", "run_metadata", ) } @@ -527,7 +544,18 @@ def _create_thread_and_run(self, input: dict, thread: dict) -> Any: params = { k: v for k, v in input.items() - if k in ("instructions", "model", "tools", "run_metadata") + if k + in ( + "instructions", + "model", + "tools", + "parallel_tool_calls", + "top_p", + "temperature", + "max_completion_tokens", + "max_prompt_tokens", + "run_metadata", + ) } run = self.client.beta.threads.create_and_run( assistant_id=self.assistant_id, @@ -651,6 +679,11 @@ async def _acreate_run(self, input: dict) -> Any: "model", "tools", "additional_instructions", + "parallel_tool_calls", + "top_p", + "temperature", + "max_completion_tokens", + "max_prompt_tokens", "run_metadata", ) } @@ -664,7 +697,18 @@ async def _acreate_thread_and_run(self, input: dict, thread: dict) -> Any: params = { k: v for k, v in input.items() - if k in ("instructions", "model", "tools", "run_metadata") + if k + in ( + "instructions", + "model", + "tools", + "parallel_tool_calls", + "top_p", + "temperature", + "max_completion_tokens", + "max_prompt_tokens", + "run_metadata", + ) } run = await self.async_client.beta.threads.create_and_run( assistant_id=self.assistant_id, From 30badd7a32727cd962d791c27a9f62a0f23c3ac7 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Tue, 14 Jan 2025 18:01:06 -0800 Subject: [PATCH 19/47] packages: update mongodb folder (#29217) --- libs/packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/packages.yml b/libs/packages.yml index e95f8c37c1b94..d7cfab1c22380 100644 --- a/libs/packages.yml +++ b/libs/packages.yml @@ -94,7 +94,7 @@ packages: downloads: 232463 downloads_updated_at: '2024-12-23T20:10:11.816059+00:00' - name: langchain-mongodb - path: libs/mongodb + path: libs/langchain-mongodb repo: langchain-ai/langchain-mongodb provider_page: mongodb_atlas js: '@langchain/mongodb' From b05543c69b370fbcde2125d45b22ad195c2af815 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Tue, 14 Jan 2025 18:23:01 -0800 Subject: [PATCH 20/47] packages: disable mongodb for api docs (#29218) --- libs/packages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/packages.yml b/libs/packages.yml index d7cfab1c22380..ff1f661aaf4d2 100644 --- a/libs/packages.yml +++ b/libs/packages.yml @@ -100,6 +100,7 @@ packages: js: '@langchain/mongodb' downloads: 113328 downloads_updated_at: '2024-12-23T20:10:11.816059+00:00' + disabled: true - name: langchain-nomic path: libs/partners/nomic repo: langchain-ai/langchain From 44b41b699c3815206413125ec58b6cca601ee438 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Tue, 14 Jan 2025 19:52:00 -0800 Subject: [PATCH 21/47] docs: api docs build folder prep update (#29220) --- .github/scripts/prep_api_docs_build.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/scripts/prep_api_docs_build.py b/.github/scripts/prep_api_docs_build.py index 175f7b6c9a766..acc0c257680fb 100644 --- a/.github/scripts/prep_api_docs_build.py +++ b/.github/scripts/prep_api_docs_build.py @@ -64,19 +64,23 @@ def main(): try: # Load packages configuration package_yaml = load_packages_yaml() - packages = [ + + # Clean target directories + clean_target_directories([ p for p in package_yaml["packages"] - if not p.get("disabled", False) - and p["repo"].startswith("langchain-ai/") + if p["repo"].startswith("langchain-ai/") and p["repo"] != "langchain-ai/langchain" - ] - - # Clean target directories - clean_target_directories(packages) + ]) # Move libraries to their new locations - move_libraries(packages) + move_libraries([ + p + for p in package_yaml["packages"] + if not p.get("disabled", False) + and p["repo"].startswith("langchain-ai/") + and p["repo"] != "langchain-ai/langchain" + ]) print("Library sync completed successfully!") From 21eb39dff0dea4dfcf75426ca18eda3ce2e32d42 Mon Sep 17 00:00:00 2001 From: Mohammad Mohtashim <45242107+keenborder786@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:44:53 +0500 Subject: [PATCH 22/47] [Community]: AzureOpenAIWhisperParser Authenication Fix (#29135) - **Description:** `AzureOpenAIWhisperParser` authentication fix as stated in the issue. - **Issue:** #29133 --- .../langchain_community/document_loaders/parsers/audio.py | 2 +- .../document_loaders/parsers/test_azure_whisper_parser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/parsers/audio.py b/libs/community/langchain_community/document_loaders/parsers/audio.py index 32ced08260187..fc4a9704deb2f 100644 --- a/libs/community/langchain_community/document_loaders/parsers/audio.py +++ b/libs/community/langchain_community/document_loaders/parsers/audio.py @@ -158,7 +158,7 @@ def __init__( azure_endpoint=self.azure_endpoint, api_version=self.api_version, max_retries=self.max_retries, - azure_ad_token=self.azure_ad_token_provider, + azure_ad_token_provider=self.azure_ad_token_provider, ) else: if self.api_key: diff --git a/libs/community/tests/unit_tests/document_loaders/parsers/test_azure_whisper_parser.py b/libs/community/tests/unit_tests/document_loaders/parsers/test_azure_whisper_parser.py index d48970534c1db..eb88677a7e4b8 100644 --- a/libs/community/tests/unit_tests/document_loaders/parsers/test_azure_whisper_parser.py +++ b/libs/community/tests/unit_tests/document_loaders/parsers/test_azure_whisper_parser.py @@ -32,7 +32,7 @@ def test_azure_openai_whisper(mock_client: Mock) -> None: azure_endpoint=endpoint, api_version=version, max_retries=3, - azure_ad_token=None, + azure_ad_token_provider=None, ) assert parser._client == mock_client() From 05554265b4c3b3c72e240ef541571e0e40d8e5e1 Mon Sep 17 00:00:00 2001 From: Jin Hyung Ahn Date: Wed, 15 Jan 2025 23:56:23 +0900 Subject: [PATCH 23/47] community: Fix ConfluenceLoader load() failure caused by deleted pages (#29232) ## Description This PR modifies the is_public_page function in ConfluenceLoader to prevent exceptions caused by deleted pages during the execution of ConfluenceLoader.process_pages(). **Example scenario:** Consider the following usage of ConfluenceLoader: ```python import os from langchain_community.document_loaders import ConfluenceLoader loader = ConfluenceLoader( url=os.getenv("BASE_URL"), token=os.getenv("TOKEN"), max_pages=1000, cql=f'type=page and lastmodified >= "2020-01-01 00:00"', include_restricted_content=False, ) # Raised Exception : HTTPError: Outdated version/old_draft/trashed? Cannot find content Please provide valid ContentId. documents = loader.load() ``` If a deleted page exists within the query result, the is_public_page function would previously raise an exception when calling get_all_restrictions_for_content, causing the loader.load() process to fail for all pages. By adding a pre-check for the page's "current" status, unnecessary API calls to get_all_restrictions_for_content for non-current pages are avoided. This fix ensures that such pages are skipped without affecting the rest of the loading process. ## Issue N/A (No specific issue number) ## Dependencies No new dependencies are introduced with this change. ## Twitter handle [@zenoengine](https://x.com/zenoengine) --- .../langchain_community/document_loaders/confluence.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/confluence.py b/libs/community/langchain_community/document_loaders/confluence.py index 225488c9ceb55..d8c5efa87482d 100644 --- a/libs/community/langchain_community/document_loaders/confluence.py +++ b/libs/community/langchain_community/document_loaders/confluence.py @@ -523,11 +523,14 @@ def paginate_request(self, retrieval_method: Callable, **kwargs: Any) -> List: def is_public_page(self, page: dict) -> bool: """Check if a page is publicly accessible.""" + + if page["status"] != "current": + return False + restrictions = self.confluence.get_all_restrictions_for_content(page["id"]) return ( - page["status"] == "current" - and not restrictions["read"]["restrictions"]["user"]["results"] + not restrictions["read"]["restrictions"]["user"]["results"] and not restrictions["read"]["restrictions"]["group"]["results"] ) From 4278046329691013c61753c2ced14ffd65502e10 Mon Sep 17 00:00:00 2001 From: Syed Baqar Abbas <76920434+SyedBaqarAbbas@users.noreply.github.com> Date: Wed, 15 Jan 2025 20:00:03 +0500 Subject: [PATCH 24/47] [fix] Convert table names to list for compatibility in SQLDatabase (#29229) - [langchain_community.utilities.SQLDatabase] **[fix] Convert table names to list for compatibility in SQLDatabase**: - The issue #29227 is being fixed here - The "package" modified is community - The issue lied in this block of code: https://github.com/langchain-ai/langchain/blob/44b41b699c3815206413125ec58b6cca601ee438/libs/community/langchain_community/utilities/sql_database.py#L72-L77 - [langchain_community.utilities.SQLDatabase] **[fix] Convert table names to list for compatibility in SQLDatabase**: - **Description:** When the SQLDatabase is initialized, it runs a code `self._inspector.get_table_names(schema=schema)` which expects an output of list. However, with some connectors (such as snowflake) the data type returned could be another iterable. This results in a type error when concatenating the table_names to view_names. I have added explicit type casting to prevent this. - **Issue:** The issue #29227 is being fixed here - **Dependencies:** None - **Twitter handle:** @BaqarAbbas2001 ## Additional Information When the following method is called for a Snowflake database: https://github.com/langchain-ai/langchain/blob/44b41b699c3815206413125ec58b6cca601ee438/libs/community/langchain_community/utilities/sql_database.py#L75 Snowflake under the hood calls: ```python from snowflake.sqlalchemy.snowdialect import SnowflakeDialect SnowflakeDialect.get_table_names ``` This method returns a `dict_keys()` object which is incompatible to concatenate with a list and results in a `TypeError` ### Relevant Library Versions - **snowflake-sqlalchemy**: 1.7.2 - **snowflake-connector-python**: 3.12.4 - **sqlalchemy**: 2.0.20 - **langchain_community**: 0.3.14 --- libs/community/langchain_community/utilities/sql_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/utilities/sql_database.py b/libs/community/langchain_community/utilities/sql_database.py index 85f0a1076770d..2a091fd2cf3cd 100644 --- a/libs/community/langchain_community/utilities/sql_database.py +++ b/libs/community/langchain_community/utilities/sql_database.py @@ -72,7 +72,7 @@ def __init__( # including view support by adding the views as well as tables to the all # tables list if view_support is True self._all_tables = set( - self._inspector.get_table_names(schema=schema) + list(self._inspector.get_table_names(schema=schema)) + (self._inspector.get_view_names(schema=schema) if view_support else []) ) From d1cf10373be05241ef2d74aa7edbee2a23b6151f Mon Sep 17 00:00:00 2001 From: Sohaib Athar Date: Wed, 15 Jan 2025 20:09:51 +0500 Subject: [PATCH 25/47] Update elasticsearch_retriever.ipynb (#29223) docs: fix typo (connection) - **Twitter handle:** @ReallyVirtual --- docs/docs/integrations/retrievers/elasticsearch_retriever.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/retrievers/elasticsearch_retriever.ipynb b/docs/docs/integrations/retrievers/elasticsearch_retriever.ipynb index bf64cc957555f..b15fcb25f4cdd 100644 --- a/docs/docs/integrations/retrievers/elasticsearch_retriever.ipynb +++ b/docs/docs/integrations/retrievers/elasticsearch_retriever.ipynb @@ -107,7 +107,7 @@ "source": [ "### Configure\n", "\n", - "Here we define the conncection to Elasticsearch. In this example we use a locally running instance. Alternatively, you can make an account in [Elastic Cloud](https://cloud.elastic.co/) and start a [free trial](https://www.elastic.co/cloud/cloud-trial-overview)." + "Here we define the connection to Elasticsearch. In this example we use a locally running instance. Alternatively, you can make an account in [Elastic Cloud](https://cloud.elastic.co/) and start a [free trial](https://www.elastic.co/cloud/cloud-trial-overview)." ] }, { From bea5798b04f3dd739cf598000779a1e5ab487d5a Mon Sep 17 00:00:00 2001 From: Kostadin Devedzhiev <48786151+kostadindev@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:10:14 -0500 Subject: [PATCH 26/47] docs: Fix typo in retrievers documentation: 'An vectorstore' -> 'A vectorstore' (#29221) - [x] **PR title**: "docs: Fix typo in documentation" - [x] **PR message**: - **Description:** Fixed a typo in the documentation, changing "An vectorstore" to "A vector store" for grammatical accuracy. - **Issue:** N/A (no issue filed for this typo fix) - **Dependencies:** None - **Twitter handle:** N/A - [x] **Add tests and docs**: This is a minor documentation fix that doesn't require additional tests or example notebooks. - [x] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. See contribution guidelines for more: https://python.langchain.com/docs/contributing/ --- docs/docs/concepts/retrievers.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/concepts/retrievers.mdx b/docs/docs/concepts/retrievers.mdx index 1fb55a3f1c588..baa3fc7c174e1 100644 --- a/docs/docs/concepts/retrievers.mdx +++ b/docs/docs/concepts/retrievers.mdx @@ -90,7 +90,7 @@ LangChain has retrievers for many popular lexical search algorithms / engines. ### Vector store [Vector stores](/docs/concepts/vectorstores/) are a powerful and efficient way to index and retrieve unstructured data. -An vectorstore can be used as a retriever by calling the `as_retriever()` method. +A vectorstore can be used as a retriever by calling the `as_retriever()` method. ```python vectorstore = MyVectorStore() From 4867fe7ac8b05779ba8ead9151ab99622938b0c1 Mon Sep 17 00:00:00 2001 From: TheSongg <145535169+TheSongg@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:11:26 +0800 Subject: [PATCH 27/47] [langchain_community.llms.xinference]: fix error in xinference.py (#29216) - [ ] **PR title**: [langchain_community.llms.xinference]: fix error in xinference.py - [ ] **PR message**: - The old code raised an ValidationError: pydantic_core._pydantic_core.ValidationError: 1 validation error for Xinference when import Xinference from xinference.py. This issue has been resolved by adjusting it's type and default value. File "/media/vdc/python/lib/python3.10/site-packages/pydantic/main.py", line 212, in __init__ validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) pydantic_core._pydantic_core.ValidationError: 1 validation error for Xinference client Field required [type=missing, input_value={'server_url': 'http://10...t4', 'model_kwargs': {}}, input_type=dict] For further information visit https://errors.pydantic.dev/2.9/v/missing - [ ] **tests**: from langchain_community.llms import Xinference llm = Xinference( server_url="http://0.0.0.0:9997", # replace your xinference server url model_uid={model_uid} # replace model_uid with the model UID return from launching the model ) --- libs/community/langchain_community/llms/xinference.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/llms/xinference.py b/libs/community/langchain_community/llms/xinference.py index 8a6cb2ee56d04..ada9f8b1f1084 100644 --- a/libs/community/langchain_community/llms/xinference.py +++ b/libs/community/langchain_community/llms/xinference.py @@ -81,7 +81,7 @@ class Xinference(LLM): """ # noqa: E501 - client: Any + client: Optional[Any] = None server_url: Optional[str] """URL of the xinference server""" model_uid: Optional[str] @@ -156,6 +156,8 @@ def _call( Returns: The generated string by the model. """ + if self.client is None: + raise ValueError("Client is not initialized!") model = self.client.get_model(self.model_uid) generate_config: "LlamaCppGenerateConfig" = kwargs.get("generate_config", {}) From 288613d36180553605b0169de63d5cf9f2323dd9 Mon Sep 17 00:00:00 2001 From: Mohammad Mohtashim <45242107+keenborder786@users.noreply.github.com> Date: Wed, 15 Jan 2025 20:18:06 +0500 Subject: [PATCH 28/47] (text-splitters): Small Fix in `_process_html` for HTMLSemanticPreservingSplitter to properly extract the metadata. (#29215) - **Description:** Include `main` in the list of elements whose child elements needs to be processed for splitting the HTML. - **Issue:** #29184 --- libs/text-splitters/langchain_text_splitters/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/text-splitters/langchain_text_splitters/html.py b/libs/text-splitters/langchain_text_splitters/html.py index 212a9abaa7b95..3613937d9991f 100644 --- a/libs/text-splitters/langchain_text_splitters/html.py +++ b/libs/text-splitters/langchain_text_splitters/html.py @@ -696,7 +696,7 @@ def _process_element( placeholder_count: int, ) -> Tuple[List[Document], Dict[str, str], List[str], Dict[str, str], int]: for elem in element: - if elem.name.lower() in ["html", "body", "div"]: + if elem.name.lower() in ["html", "body", "div", "main"]: children = elem.find_all(recursive=False) ( documents, From 1a38948ee35bb0ba7a4446a86841a5da107ad4f7 Mon Sep 17 00:00:00 2001 From: Mehdi <8142364+MehdiZare@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:31:01 -0500 Subject: [PATCH 29/47] Mehdi zare/fmp data doc (#29219) Title: community: add Financial Modeling Prep (FMP) API integration Description: Adding LangChain integration for Financial Modeling Prep (FMP) API to enable semantic search and structured tool creation for financial data endpoints. This integration provides semantic endpoint search using vector stores and automatic tool creation with proper typing and error handling. Users can discover relevant financial endpoints using natural language queries and get properly typed LangChain tools for discovered endpoints. Issue: N/A Dependencies: fmp-data>=0.3.1 langchain-core>=0.1.0 faiss-cpu tiktoken Twitter handle: @mehdizarem Unit tests and example notebook have been added: Tests are in tests/integration_tests/est_tools.py and tests/unit_tests/test_tools.py Example notebook is in docs/tools.ipynb All format, lint and test checks pass: pytest mypy . Dependencies are imported within functions and not added to pyproject.toml. The changes are backwards compatible and only affect the community package. --------- Co-authored-by: mehdizare Co-authored-by: Chester Curme --- docs/docs/integrations/providers/fmp-data.mdx | 21 + docs/docs/integrations/tools/fmp-data.ipynb | 412 ++++++++++++++++++ libs/packages.yml | 4 + 3 files changed, 437 insertions(+) create mode 100644 docs/docs/integrations/providers/fmp-data.mdx create mode 100644 docs/docs/integrations/tools/fmp-data.ipynb diff --git a/docs/docs/integrations/providers/fmp-data.mdx b/docs/docs/integrations/providers/fmp-data.mdx new file mode 100644 index 0000000000000..b2ab522fb03f3 --- /dev/null +++ b/docs/docs/integrations/providers/fmp-data.mdx @@ -0,0 +1,21 @@ +# FMP Data (Financial Data Prep) + +> [FMP-Data](https://pypi.org/project/fmp-data/) is a python package for connecting to +> Financial Data Prep API. It simplifies how you can access production quality data. + + +## Installation and Setup + +Get an `FMP Data` API key by +visiting [this page](https://site.financialmodelingprep.com/pricing-plans?couponCode=mehdi). + and set it as an environment variable (`FMP_API_KEY`). + +Then, install [langchain-fmp-data](https://pypi.org/project/langchain-fmp-data/). + +## Tools + +See an [example](https://github.com/MehdiZare/langchain-fmp-data/tree/main/docs). + +```python +from langchain_fmp_data import FMPDataTool, FMPDataToolkit +``` diff --git a/docs/docs/integrations/tools/fmp-data.ipynb b/docs/docs/integrations/tools/fmp-data.ipynb new file mode 100644 index 0000000000000..8ad6cfcca2e12 --- /dev/null +++ b/docs/docs/integrations/tools/fmp-data.ipynb @@ -0,0 +1,412 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "62828c19159a0da8", + "metadata": {}, + "source": [ + "# FMP Data\n", + "\n", + "Access financial market data through natural language queries.\n", + "\n", + "## Overview\n", + "\n", + "The FMP (Financial Modeling Prep) LangChain integration provides a seamless way to access financial market data through natural language queries. This integration offers two main components:\n", + "\n", + "- `FMPDataToolkit`: Creates collections of tools based on natural language queries\n", + "- `FMPDataTool`: A single unified tool that automatically selects and uses the appropriate endpoints\n", + "\n", + "The integration leverages LangChain's semantic search capabilities to match user queries with the most relevant FMP API endpoints, making financial data access more intuitive and efficient.\n", + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3faf15d8ae5f8500", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -U langchain-fmp-data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "647f66796446eb0f", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# Replace with your actual API keys\n", + "os.environ[\"FMP_API_KEY\"] = \"your-fmp-api-key\" # pragma: allowlist secret\n", + "os.environ[\"OPENAI_API_KEY\"] = \"your-openai-api-key\" # pragma: allowlist secret" + ] + }, + { + "cell_type": "markdown", + "id": "b5de291f5c2f67a2", + "metadata": {}, + "source": [ + "It's also helpful (but not needed) to set up [LangSmith](https://smith.langchain.com/) for best-in-class observability:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb86baf3da526812", + "metadata": {}, + "outputs": [], + "source": [ + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "d4d6a4b9ac69569f", + "metadata": {}, + "source": [ + "## Instantiation\n", + "There are two main ways to instantiate the FMP LangChain integration:\n", + "1. Using FMPDataToolkit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff5f451182995407", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_fmp_data import FMPDataToolkit\n", + "\n", + "query = \"Get stock market prices and technical indicators\"\n", + "# Basic instantiation\n", + "toolkit = FMPDataToolkit(query=query)\n", + "\n", + "# Instantiation with specific query focus\n", + "market_toolkit = FMPDataToolkit(\n", + " query=query,\n", + " num_results=5,\n", + ")\n", + "\n", + "# Instantiation with custom configuration\n", + "custom_toolkit = FMPDataToolkit(\n", + " query=\"Financial analysis\",\n", + " num_results=3,\n", + " similarity_threshold=0.4,\n", + " cache_dir=\"/custom/cache/path\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8cd16450e03ea000", + "metadata": {}, + "source": [ + "2. Using FMPDataTool" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c924dd0e34b3db9", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_fmp_data import FMPDataTool\n", + "from langchain_fmp_data.tools import ResponseFormat\n", + "\n", + "# Basic instantiation\n", + "tool = FMPDataTool()\n", + "\n", + "# Advanced instantiation with custom settings\n", + "advanced_tool = FMPDataTool(\n", + " max_iterations=50,\n", + " temperature=0.2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "499dde5c011b44b6", + "metadata": {}, + "source": [ + "## Invocation\n", + "The tools can be invoked in several ways:\n", + "\n", + "### Direct Invocation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2fc685c536bdc54", + "metadata": {}, + "outputs": [], + "source": [ + "# Using FMPDataTool\n", + "tool_direct = FMPDataTool()\n", + "\n", + "# Basic query\n", + "# fmt: off\n", + "result = tool.invoke({\"query\": \"What's Apple's current stock price?\"})\n", + "# fmt: on\n", + "\n", + "# Advanced query with specific format\n", + "# fmt: off\n", + "detailed_result = tool_direct.invoke(\n", + " {\n", + " \"query\": \"Compare Tesla and Ford's profit margins\",\n", + " \"response_format\": ResponseFormat.BOTH,\n", + " }\n", + ")\n", + "# fmt: on" + ] + }, + { + "cell_type": "markdown", + "id": "3735e50bdeb55c4", + "metadata": {}, + "source": [ + "### Using with LangChain Agents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73b3684edd3ddbce", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "# Setup\n", + "llm = ChatOpenAI(temperature=0)\n", + "toolkit = FMPDataToolkit(\n", + " query=\"Stock analysis\",\n", + " num_results=3,\n", + ")\n", + "tools = toolkit.get_tools()\n", + "\n", + "# Create agent\n", + "prompt = \"You are a helpful assistant. Answer the user's questions based on the provided context.\"\n", + "agent = create_openai_functions_agent(llm, tools, prompt)\n", + "agent_executor = AgentExecutor(\n", + " agent=agent,\n", + " tools=tools,\n", + ")\n", + "\n", + "# Run query\n", + "# fmt: off\n", + "response = agent_executor.invoke({\"input\": \"What's the PE ratio of Microsoft?\"})\n", + "# fmt: on" + ] + }, + { + "cell_type": "markdown", + "id": "73654bed2bd79c50", + "metadata": {}, + "source": [ + "## Advanced Usage\n", + "You can customize the tool's behavior:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20fa3c2ed5204299", + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize with custom settings\n", + "advanced_tool = FMPDataTool(\n", + " max_iterations=50, # Increase max iterations for complex queries\n", + " temperature=0.2, # Adjust temperature for more/less focused responses\n", + ")\n", + "\n", + "# Example of a complex multi-part analysis\n", + "query = \"\"\"\n", + "Analyze Apple's financial health by:\n", + "1. Examining current ratios and debt levels\n", + "2. Comparing profit margins to industry average\n", + "3. Looking at cash flow trends\n", + "4. Assessing growth metrics\n", + "\"\"\"\n", + "# fmt: off\n", + "response = advanced_tool.invoke(\n", + " {\n", + " \"query\": query,\n", + " \"response_format\": ResponseFormat.BOTH}\n", + ")\n", + "# fmt: on\n", + "print(\"Detailed Financial Analysis:\")\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "id": "f2d5e31becc88d70", + "metadata": {}, + "source": [ + "## Chaining\n", + "You can chain the tool similar to other tools simply by creating a chain with desired model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad1d8e8575c7bc7", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "# Setup\n", + "llm = ChatOpenAI(temperature=0)\n", + "toolkit = FMPDataToolkit(query=\"Stock analysis\", num_results=3)\n", + "tools = toolkit.get_tools()\n", + "\n", + "llm_with_tools = llm.bind(functions=tools)\n", + "output_parser = StrOutputParser()\n", + "# Create chain\n", + "runner = llm_with_tools | output_parser\n", + "\n", + "# Run chain\n", + "# fmt: off\n", + "response = runner.invoke(\n", + " {\n", + " \"input\": \"What's the PE ratio of Microsoft?\"\n", + " }\n", + ")\n", + "# fmt: on" + ] + }, + { + "cell_type": "markdown", + "id": "2fe9b99e6bd5d3bb", + "metadata": {}, + "source": [ + "## API reference\n", + "\n", + "### FMPDataToolkit\n", + "Main class for creating collections of FMP API tools:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd71cf7dda4e1579", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any\n", + "\n", + "from langchain.tools import Tool\n", + "\n", + "\n", + "class FMPDataToolkit:\n", + " \"\"\"Creates a collection of FMP data tools based on queries.\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " query: str | None = None,\n", + " num_results: int = 3,\n", + " similarity_threshold: float = 0.3,\n", + " cache_dir: str | None = None,\n", + " ): ...\n", + "\n", + " def get_tools(self) -> list[Tool]:\n", + " \"\"\"Returns a list of relevant FMP API tools based on the query.\"\"\"\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "id": "8fe43c9a7cf7216c", + "metadata": {}, + "source": [ + "### FMPDataTool\n", + "Unified tool that automatically selects appropriate FMP endpoints:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fbd891b3798e529", + "metadata": {}, + "outputs": [], + "source": [ + "# fmt: off\n", + "class FMPDataTool:\n", + " \"\"\"Single unified tool for accessing FMP data through natural language.\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " max_iterations: int = 3,\n", + " temperature: float = 0.0,\n", + " ): ...\n", + "\n", + " def invoke(\n", + " self,\n", + " input: dict[str, Any],\n", + " ) -> str | dict[str, Any]:\n", + " \"\"\"Execute a natural language query against FMP API.\"\"\"\n", + " ...\n", + "\n", + "# fmt: on" + ] + }, + { + "cell_type": "markdown", + "id": "6b336afd0cdf2bd5", + "metadata": {}, + "source": [ + "### ResponseFormat\n", + "Enum for controlling response format:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb57dbcb88d8d118", + "metadata": {}, + "outputs": [], + "source": [ + "from enum import Enum\n", + "\n", + "\n", + "class ResponseFormat(str, Enum):\n", + " RAW = \"raw\" # Raw API response\n", + " ANALYSIS = \"text\" # Natural language analysis\n", + " BOTH = \"both\" # Both raw data and analysis" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/packages.yml b/libs/packages.yml index ff1f661aaf4d2..d8bf7ac1ccbd2 100644 --- a/libs/packages.yml +++ b/libs/packages.yml @@ -342,3 +342,7 @@ packages: path: . repo: hyperbrowserai/langchain-hyperbrowser downloads: 0 +- name: langchain-fmp-data + path: . + repo: MehdiZare/langchain-fmp-data + downloads: 0 From eaf2fb287f6a14717b06872d6451c654ab2b6825 Mon Sep 17 00:00:00 2001 From: Nadeem Sajjad Date: Thu, 16 Jan 2025 00:48:07 +0530 Subject: [PATCH 30/47] community(pypdfloader): added page_label in metadata for pypdf loader (#29225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description ## Summary This PR adds support for handling multi-labeled page numbers in the **PyPDFLoader**. Some PDFs use complex page numbering systems where the actual content may begin after multiple introductory pages. The page_label field helps accurately reflect the document’s page structure, making it easier to handle such cases during document parsing. ## Motivation This feature improves document parsing accuracy by allowing users to access the actual page labels instead of relying only on the physical page numbers. This is particularly useful for documents where the first few pages have roman numerals or other non-standard page labels. ## Use Case This feature is especially useful for **Retrieval-Augmented Generation** (RAG) systems where users may reference page numbers when asking questions. Some PDFs have both labeled page numbers (like roman numerals for introductory sections) and index-based page numbers. For example, a user might ask: "What is mentioned on page 5?" The system can now check both: • **Index-based page number** (page) • **Labeled page number** (page_label) This dual-check helps improve retrieval accuracy. Additionally, the results can be validated with an **agent or tool** to ensure the retrieved pages match the user’s query contextually. ## Code Changes - Added a page_label field to the metadata of the Document class in **PyPDFLoader**. - Implemented support for retrieving page_label from the pdf_reader.page_labels. - Created a test case (test_pypdf_loader_with_multi_label_page_numbers) with a sample PDF containing multi-labeled pages (geotopo-komprimiert.pdf) [[Source of pdf](https://github.com/py-pdf/sample-files/blob/main/009-pdflatex-geotopo/GeoTopo-komprimiert.pdf)]. - Updated existing tests to ensure compatibility and verify page_label extraction. ## Tests Added - Added a new test case for a PDF with multi-labeled pages. - Verified both page and page_label metadata fields are correctly extracted. ## Screenshots image --- .../document_loaders/parsers/pdf.py | 6 ++++- .../sample_documents/geotopo-komprimiert.pdf | Bin 0 -> 209854 bytes .../unit_tests/document_loaders/test_pdf.py | 22 ++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 libs/community/tests/unit_tests/document_loaders/sample_documents/geotopo-komprimiert.pdf diff --git a/libs/community/langchain_community/document_loaders/parsers/pdf.py b/libs/community/langchain_community/document_loaders/parsers/pdf.py index 00b4510ee660d..063f863869f87 100644 --- a/libs/community/langchain_community/document_loaders/parsers/pdf.py +++ b/libs/community/langchain_community/document_loaders/parsers/pdf.py @@ -123,7 +123,11 @@ def _extract_text_from_page(page: pypdf.PageObject) -> str: Document( page_content=_extract_text_from_page(page=page) + self._extract_images_from_page(page), - metadata={"source": blob.source, "page": page_number}, + metadata={ + "source": blob.source, + "page": page_number, + "page_label": pdf_reader.page_labels[page_number], + }, # type: ignore[attr-defined] ) for page_number, page in enumerate(pdf_reader.pages) diff --git a/libs/community/tests/unit_tests/document_loaders/sample_documents/geotopo-komprimiert.pdf b/libs/community/tests/unit_tests/document_loaders/sample_documents/geotopo-komprimiert.pdf new file mode 100644 index 0000000000000000000000000000000000000000..370322aee32fa12e7b3623faa184d269e3ca08a2 GIT binary patch literal 209854 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%0DpYr-nZY|9`?>vM__9 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≧0S=2aXthtr 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

L-)lN6%HC}-j*LE|wJ8?a7BOpm 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 literal 0 HcmV?d00001 diff --git a/libs/community/tests/unit_tests/document_loaders/test_pdf.py b/libs/community/tests/unit_tests/document_loaders/test_pdf.py index ae7356ea4952e..62d4fe8cc6e45 100644 --- a/libs/community/tests/unit_tests/document_loaders/test_pdf.py +++ b/libs/community/tests/unit_tests/document_loaders/test_pdf.py @@ -12,6 +12,10 @@ Path(__file__).parent.parent / "document_loaders/sample_documents/layout-parser-paper.pdf" ) +path_to_multi_label_page_numbers_pdf = ( + Path(__file__).parent.parent + / "document_loaders/sample_documents/geotopo-komprimiert.pdf" +) path_to_layout_pdf_txt = ( Path(__file__).parent.parent.parent / "integration_tests/examples/layout-parser-paper-page-1.txt" @@ -32,6 +36,7 @@ def test_pypdf_loader() -> None: assert len(docs) == 16 for page, doc in enumerate(docs): assert doc.metadata["page"] == page + assert doc.metadata["page_label"] == str(page + 1) assert doc.metadata["source"].endswith("layout-parser-paper.pdf") assert len(doc.page_content) > 10 @@ -49,6 +54,7 @@ def test_pypdf_loader_with_layout() -> None: assert len(docs) == 16 for page, doc in enumerate(docs): assert doc.metadata["page"] == page + assert doc.metadata["page_label"] == str(page + 1) assert doc.metadata["source"].endswith("layout-parser-paper.pdf") assert len(doc.page_content) > 10 @@ -60,3 +66,19 @@ def test_pypdf_loader_with_layout() -> None: cleaned_first_page = re.sub(r"\x00", "", first_page) cleaned_expected = re.sub(r"\x00", "", expected) assert cleaned_first_page == cleaned_expected + + +@pytest.mark.requires("pypdf") +def test_pypdf_loader_with_multi_labled_page_numbers() -> None: + """Test PyPDFLoader with a pdf that contains multi-labled page numbers.""" + loader = PyPDFLoader(str(path_to_multi_label_page_numbers_pdf)) + docs = loader.load() + + assert len(docs) == 7 + + assert docs[0].metadata["page"] == 0 + assert docs[0].metadata["page_label"] == "i" + + # Since the actual page numbers in this pdf starts from 4th page + assert docs[3].metadata["page"] == 3 + assert docs[3].metadata["page_label"] == "1" From 1051fa57299a23534fc43d6af4aa350cc31aa8ee Mon Sep 17 00:00:00 2001 From: Nithish Raghunandanan Date: Wed, 15 Jan 2025 21:37:27 +0100 Subject: [PATCH 31/47] couchbase: Migrate couchbase partner package to different repo (#29239) **Description:** Migrate the couchbase partner package to [Couchbase-Ecosystem](https://github.com/Couchbase-Ecosystem/langchain-couchbase) org --- libs/packages.yml | 4 +- libs/partners/couchbase/.gitignore | 3 - libs/partners/couchbase/LICENSE | 21 - libs/partners/couchbase/Makefile | 64 - libs/partners/couchbase/README.md | 43 +- .../couchbase/langchain_couchbase/__init__.py | 10 - .../couchbase/langchain_couchbase/cache.py | 386 ---- .../chat_message_histories.py | 269 --- .../couchbase/langchain_couchbase/py.typed | 0 .../langchain_couchbase/vectorstores.py | 739 ------- libs/partners/couchbase/poetry.lock | 1790 ----------------- libs/partners/couchbase/pyproject.toml | 81 - .../couchbase/scripts/check_imports.py | 17 - .../couchbase/scripts/lint_imports.sh | 18 - libs/partners/couchbase/tests/__init__.py | 0 .../tests/integration_tests/__init__.py | 0 .../tests/integration_tests/test_cache.py | 218 -- .../test_chat_message_history.py | 294 --- .../tests/integration_tests/test_compile.py | 7 - .../integration_tests/test_vector_store.py | 396 ---- .../couchbase/tests/unit_tests/__init__.py | 0 .../tests/unit_tests/test_imports.py | 12 - .../tests/unit_tests/test_vectorstore.py | 1 - libs/partners/couchbase/tests/utils.py | 136 -- 24 files changed, 4 insertions(+), 4505 deletions(-) delete mode 100644 libs/partners/couchbase/.gitignore delete mode 100644 libs/partners/couchbase/LICENSE delete mode 100644 libs/partners/couchbase/Makefile delete mode 100644 libs/partners/couchbase/langchain_couchbase/__init__.py delete mode 100644 libs/partners/couchbase/langchain_couchbase/cache.py delete mode 100644 libs/partners/couchbase/langchain_couchbase/chat_message_histories.py delete mode 100644 libs/partners/couchbase/langchain_couchbase/py.typed delete mode 100644 libs/partners/couchbase/langchain_couchbase/vectorstores.py delete mode 100644 libs/partners/couchbase/poetry.lock delete mode 100644 libs/partners/couchbase/pyproject.toml delete mode 100644 libs/partners/couchbase/scripts/check_imports.py delete mode 100755 libs/partners/couchbase/scripts/lint_imports.sh delete mode 100644 libs/partners/couchbase/tests/__init__.py delete mode 100644 libs/partners/couchbase/tests/integration_tests/__init__.py delete mode 100644 libs/partners/couchbase/tests/integration_tests/test_cache.py delete mode 100644 libs/partners/couchbase/tests/integration_tests/test_chat_message_history.py delete mode 100644 libs/partners/couchbase/tests/integration_tests/test_compile.py delete mode 100644 libs/partners/couchbase/tests/integration_tests/test_vector_store.py delete mode 100644 libs/partners/couchbase/tests/unit_tests/__init__.py delete mode 100644 libs/partners/couchbase/tests/unit_tests/test_imports.py delete mode 100644 libs/partners/couchbase/tests/unit_tests/test_vectorstore.py delete mode 100644 libs/partners/couchbase/tests/utils.py diff --git a/libs/packages.yml b/libs/packages.yml index d8bf7ac1ccbd2..8e4502e1c51b6 100644 --- a/libs/packages.yml +++ b/libs/packages.yml @@ -259,8 +259,8 @@ packages: downloads: 35495 downloads_updated_at: '2024-12-23T20:10:11.816059+00:00' - name: langchain-couchbase - path: libs/partners/couchbase - repo: langchain-ai/langchain + path: . + repo: Couchbase-Ecosystem/langchain-couchbase downloads: 347 downloads_updated_at: '2024-12-23T20:10:11.816059+00:00' - name: langchain-ollama diff --git a/libs/partners/couchbase/.gitignore b/libs/partners/couchbase/.gitignore deleted file mode 100644 index 53fd65cf3d994..0000000000000 --- a/libs/partners/couchbase/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -__pycache__ -# mypy -.mypy_cache/ diff --git a/libs/partners/couchbase/LICENSE b/libs/partners/couchbase/LICENSE deleted file mode 100644 index fc0602feecdd6..0000000000000 --- a/libs/partners/couchbase/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 LangChain, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/partners/couchbase/Makefile b/libs/partners/couchbase/Makefile deleted file mode 100644 index 5273eff70528e..0000000000000 --- a/libs/partners/couchbase/Makefile +++ /dev/null @@ -1,64 +0,0 @@ -.PHONY: all format lint test tests integration_tests docker_tests help extended_tests - -# Default target executed when no arguments are given to make. -all: help - -# Define a variable for the test file path. -TEST_FILE ?= tests/unit_tests/ -integration_test integration_tests: TEST_FILE = tests/integration_tests/ - - -# unit tests are run with the --disable-socket flag to prevent network calls -test tests: - poetry run pytest --disable-socket --allow-unix-socket $(TEST_FILE) - -test_watch: - poetry run ptw --snapshot-update --now . -- -vv $(TEST_FILE) - -# integration tests are run without the --disable-socket flag to allow network calls -integration_test integration_tests: - poetry run pytest $(TEST_FILE) - -###################### -# LINTING AND FORMATTING -###################### - -# Define a variable for Python and notebook files. -PYTHON_FILES=. -MYPY_CACHE=.mypy_cache -lint format: PYTHON_FILES=. -lint_diff format_diff: PYTHON_FILES=$(shell git diff --relative=libs/partners/couchbase --name-only --diff-filter=d master | grep -E '\.py$$|\.ipynb$$') -lint_package: PYTHON_FILES=langchain_couchbase -lint_tests: PYTHON_FILES=tests -lint_tests: MYPY_CACHE=.mypy_cache_test - -lint lint_diff lint_package lint_tests: - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff check $(PYTHON_FILES) - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff - [ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) && poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE) - -format format_diff: - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I --fix $(PYTHON_FILES) - -spell_check: - poetry run codespell --toml pyproject.toml - -spell_fix: - poetry run codespell --toml pyproject.toml -w - -check_imports: $(shell find langchain_couchbase -name '*.py') - poetry run python ./scripts/check_imports.py $^ - -###################### -# HELP -###################### - -help: - @echo '----' - @echo 'check_imports - check imports' - @echo 'format - run code formatters' - @echo 'lint - run linters' - @echo 'test - run unit tests' - @echo 'tests - run unit tests' - @echo 'test TEST_FILE= - run all tests in file' diff --git a/libs/partners/couchbase/README.md b/libs/partners/couchbase/README.md index 006de243ab237..9d5685072d44a 100644 --- a/libs/partners/couchbase/README.md +++ b/libs/partners/couchbase/README.md @@ -1,42 +1,3 @@ -# langchain-couchbase +This package has moved! -This package contains the LangChain integration with Couchbase - -## Installation - -```bash -pip install -U langchain-couchbase -``` - -## Usage - -The `CouchbaseVectorStore` class exposes the connection to the Couchbase vector store. - -```python -from langchain_couchbase.vectorstores import CouchbaseVectorStore - -from couchbase.cluster import Cluster -from couchbase.auth import PasswordAuthenticator -from couchbase.options import ClusterOptions -from datetime import timedelta - -auth = PasswordAuthenticator(username, password) -options = ClusterOptions(auth) -connect_string = "couchbases://localhost" -cluster = Cluster(connect_string, options) - -# Wait until the cluster is ready for use. -cluster.wait_until_ready(timedelta(seconds=5)) - -embeddings = OpenAIEmbeddings() - -vectorstore = CouchbaseVectorStore( - cluster=cluster, - bucket_name="", - scope_name="", - collection_name="", - embedding=embeddings, - index_name="vector-search-index", -) - -``` +https://github.com/Couchbase-Ecosystem/langchain-couchbase/tree/main/langchain_couchbase diff --git a/libs/partners/couchbase/langchain_couchbase/__init__.py b/libs/partners/couchbase/langchain_couchbase/__init__.py deleted file mode 100644 index 17d50b3c2afe2..0000000000000 --- a/libs/partners/couchbase/langchain_couchbase/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from langchain_couchbase.cache import CouchbaseCache, CouchbaseSemanticCache -from langchain_couchbase.chat_message_histories import CouchbaseChatMessageHistory -from langchain_couchbase.vectorstores import CouchbaseVectorStore - -__all__ = [ - "CouchbaseVectorStore", - "CouchbaseCache", - "CouchbaseSemanticCache", - "CouchbaseChatMessageHistory", -] diff --git a/libs/partners/couchbase/langchain_couchbase/cache.py b/libs/partners/couchbase/langchain_couchbase/cache.py deleted file mode 100644 index 52118ac610ece..0000000000000 --- a/libs/partners/couchbase/langchain_couchbase/cache.py +++ /dev/null @@ -1,386 +0,0 @@ -""" -LangChain Couchbase Caches - -Functions "_hash", "_loads_generations" and "_dumps_generations" -are duplicated in this utility from modules: - - "libs/community/langchain_community/cache.py" -""" - -import hashlib -import json -import logging -from datetime import timedelta -from typing import Any, Dict, Optional, Union - -from couchbase.cluster import Cluster -from langchain_core.caches import RETURN_VAL_TYPE, BaseCache -from langchain_core.embeddings import Embeddings -from langchain_core.load.dump import dumps -from langchain_core.load.load import loads -from langchain_core.outputs import Generation - -from langchain_couchbase.vectorstores import CouchbaseVectorStore - -logger = logging.getLogger(__file__) - - -def _hash(_input: str) -> str: - """Use a deterministic hashing approach.""" - return hashlib.md5(_input.encode()).hexdigest() - - -def _dumps_generations(generations: RETURN_VAL_TYPE) -> str: - """ - Serialization for generic RETURN_VAL_TYPE, i.e. sequence of `Generation` - - Args: - generations (RETURN_VAL_TYPE): A list of language model generations. - - Returns: - str: a single string representing a list of generations. - - This function (+ its counterpart `_loads_generations`) rely on - the dumps/loads pair with Reviver, so are able to deal - with all subclasses of Generation. - - Each item in the list can be `dumps`ed to a string, - then we make the whole list of strings into a json-dumped. - """ - return json.dumps([dumps(_item) for _item in generations]) - - -def _loads_generations(generations_str: str) -> Union[RETURN_VAL_TYPE, None]: - """ - Deserialization of a string into a generic RETURN_VAL_TYPE - (i.e. a sequence of `Generation`). - - See `_dumps_generations`, the inverse of this function. - - Args: - generations_str (str): A string representing a list of generations. - - Compatible with the legacy cache-blob format - Does not raise exceptions for malformed entries, just logs a warning - and returns none: the caller should be prepared for such a cache miss. - - Returns: - RETURN_VAL_TYPE: A list of generations. - """ - try: - generations = [loads(_item_str) for _item_str in json.loads(generations_str)] - return generations - except (json.JSONDecodeError, TypeError): - # deferring the (soft) handling to after the legacy-format attempt - pass - - try: - gen_dicts = json.loads(generations_str) - # not relying on `_load_generations_from_json` (which could disappear): - generations = [Generation(**generation_dict) for generation_dict in gen_dicts] - logger.warning( - f"Legacy 'Generation' cached blob encountered: '{generations_str}'" - ) - return generations - except (json.JSONDecodeError, TypeError): - logger.warning( - f"Malformed/unparsable cached blob encountered: '{generations_str}'" - ) - return None - - -def _validate_ttl(ttl: Optional[timedelta]) -> None: - """Validate the time to live""" - if not isinstance(ttl, timedelta): - raise ValueError(f"ttl should be of type timedelta but was {type(ttl)}.") - if ttl <= timedelta(seconds=0): - raise ValueError( - f"ttl must be greater than 0 but was {ttl.total_seconds()} seconds." - ) - - -class CouchbaseCache(BaseCache): - """Couchbase LLM Cache - LLM Cache that uses Couchbase as the backend - """ - - PROMPT = "prompt" - LLM = "llm" - RETURN_VAL = "return_val" - - def _check_bucket_exists(self) -> bool: - """Check if the bucket exists in the linked Couchbase cluster""" - bucket_manager = self._cluster.buckets() - try: - bucket_manager.get_bucket(self._bucket_name) - return True - except Exception: - return False - - def _check_scope_and_collection_exists(self) -> bool: - """Check if the scope and collection exists in the linked Couchbase bucket - Raises a ValueError if either is not found""" - scope_collection_map: Dict[str, Any] = {} - - # Get a list of all scopes in the bucket - for scope in self._bucket.collections().get_all_scopes(): - scope_collection_map[scope.name] = [] - - # Get a list of all the collections in the scope - for collection in scope.collections: - scope_collection_map[scope.name].append(collection.name) - - # Check if the scope exists - if self._scope_name not in scope_collection_map.keys(): - raise ValueError( - f"Scope {self._scope_name} not found in Couchbase " - f"bucket {self._bucket_name}" - ) - - # Check if the collection exists in the scope - if self._collection_name not in scope_collection_map[self._scope_name]: - raise ValueError( - f"Collection {self._collection_name} not found in scope " - f"{self._scope_name} in Couchbase bucket {self._bucket_name}" - ) - - return True - - def __init__( - self, - cluster: Cluster, - bucket_name: str, - scope_name: str, - collection_name: str, - ttl: Optional[timedelta] = None, - **kwargs: Dict[str, Any], - ) -> None: - """Initialize the Couchbase LLM Cache - Args: - cluster (Cluster): couchbase cluster object with active connection. - bucket_name (str): name of the bucket to store documents in. - scope_name (str): name of the scope in bucket to store documents in. - collection_name (str): name of the collection in the scope to store - documents in. - ttl (Optional[timedelta]): TTL or time for the document to live in the cache - After this time, the document will get deleted from the cache. - """ - if not isinstance(cluster, Cluster): - raise ValueError( - f"cluster should be an instance of couchbase.Cluster, " - f"got {type(cluster)}" - ) - - self._cluster = cluster - - self._bucket_name = bucket_name - self._scope_name = scope_name - self._collection_name = collection_name - - self._ttl = None - - # Check if the bucket exists - if not self._check_bucket_exists(): - raise ValueError( - f"Bucket {self._bucket_name} does not exist. " - " Please create the bucket before searching." - ) - - try: - self._bucket = self._cluster.bucket(self._bucket_name) - self._scope = self._bucket.scope(self._scope_name) - self._collection = self._scope.collection(self._collection_name) - except Exception as e: - raise ValueError( - "Error connecting to couchbase. " - "Please check the connection and credentials." - ) from e - - # Check if the scope and collection exists. Throws ValueError if they don't - try: - self._check_scope_and_collection_exists() - except Exception as e: - raise e - - # Check if the time to live is provided and valid - if ttl is not None: - _validate_ttl(ttl) - self._ttl = ttl - - def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]: - """Look up from cache based on prompt and llm_string.""" - try: - doc = self._collection.get( - self._generate_key(prompt, llm_string) - ).content_as[dict] - return _loads_generations(doc[self.RETURN_VAL]) - except Exception: - return None - - def _generate_key(self, prompt: str, llm_string: str) -> str: - """Generate the key based on prompt and llm_string.""" - return _hash(prompt + llm_string) - - def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None: - """Update cache based on prompt and llm_string.""" - doc = { - self.PROMPT: prompt, - self.LLM: llm_string, - self.RETURN_VAL: _dumps_generations(return_val), - } - document_key = self._generate_key(prompt, llm_string) - try: - if self._ttl: - self._collection.upsert( - key=document_key, - value=doc, - expiry=self._ttl, - ) - else: - self._collection.upsert(key=document_key, value=doc) - except Exception: - logger.error("Error updating cache") - - def clear(self, **kwargs: Any) -> None: - """Clear the cache. - This will delete all documents in the collection. This requires an index on the - collection. - """ - try: - query = f"DELETE FROM `{self._collection_name}`" - self._scope.query(query).execute() - except Exception: - logger.error("Error clearing cache. Please check if you have an index.") - - -class CouchbaseSemanticCache(BaseCache, CouchbaseVectorStore): - """Couchbase Semantic Cache - Cache backed by a Couchbase Server with Vector Store support - """ - - LLM = "llm_string" - RETURN_VAL = "return_val" - - def __init__( - self, - cluster: Cluster, - embedding: Embeddings, - bucket_name: str, - scope_name: str, - collection_name: str, - index_name: str, - score_threshold: Optional[float] = None, - ttl: Optional[timedelta] = None, - ) -> None: - """Initialize the Couchbase LLM Cache - Args: - cluster (Cluster): couchbase cluster object with active connection. - embedding (Embeddings): embedding model to use. - bucket_name (str): name of the bucket to store documents in. - scope_name (str): name of the scope in bucket to store documents in. - collection_name (str): name of the collection in the scope to store - documents in. - index_name (str): name of the Search index to use. - score_threshold (float): score threshold to use for filtering results. - ttl (Optional[timedelta]): TTL or time for the document to live in the cache - After this time, the document will get deleted from the cache. - """ - if not isinstance(cluster, Cluster): - raise ValueError( - f"cluster should be an instance of couchbase.Cluster, " - f"got {type(cluster)}" - ) - - self._cluster = cluster - - self._bucket_name = bucket_name - self._scope_name = scope_name - self._collection_name = collection_name - self._ttl = None - - # Check if the bucket exists - if not self._check_bucket_exists(): - raise ValueError( - f"Bucket {self._bucket_name} does not exist. " - " Please create the bucket before searching." - ) - - try: - self._bucket = self._cluster.bucket(self._bucket_name) - self._scope = self._bucket.scope(self._scope_name) - self._collection = self._scope.collection(self._collection_name) - except Exception as e: - raise ValueError( - "Error connecting to couchbase. " - "Please check the connection and credentials." - ) from e - - # Check if the scope and collection exists. Throws ValueError if they don't - try: - self._check_scope_and_collection_exists() - except Exception as e: - raise e - - self.score_threshold = score_threshold - - if ttl is not None: - _validate_ttl(ttl) - self._ttl = ttl - - # Initialize the vector store - super().__init__( - cluster=cluster, - bucket_name=bucket_name, - scope_name=scope_name, - collection_name=collection_name, - embedding=embedding, - index_name=index_name, - ) - - def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]: - """Look up from cache based on the semantic similarity of the prompt""" - search_results = self.similarity_search_with_score( - prompt, k=1, search_options={f"metadata.{self.LLM}": llm_string} - ) - if search_results: - selected_doc, score = search_results[0] - else: - return None - - # Check if the score is above the threshold if a threshold is provided - if self.score_threshold: - if score < self.score_threshold: - return None - - # Note that the llm_string might not match the vector search result. - # So if the llm_string does not match, do not return the result. - if selected_doc.metadata["llm_string"] != llm_string: - return None - - return _loads_generations(selected_doc.metadata[self.RETURN_VAL]) - - def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None: - """Update cache based on the prompt and llm_string""" - try: - self.add_texts( - texts=[prompt], - metadatas=[ - { - self.LLM: llm_string, - self.RETURN_VAL: _dumps_generations(return_val), - } - ], - ttl=self._ttl, - ) - except Exception: - logger.error("Error updating cache") - - def clear(self, **kwargs: Any) -> None: - """Clear the cache. - This will delete all documents in the collection. - This requires an index on the collection. - """ - try: - query = f"DELETE FROM `{self._collection_name}`" - self._scope.query(query).execute() - except Exception: - logger.error("Error clearing cache. Please check if you have an index.") diff --git a/libs/partners/couchbase/langchain_couchbase/chat_message_histories.py b/libs/partners/couchbase/langchain_couchbase/chat_message_histories.py deleted file mode 100644 index 172b79b9af3f7..0000000000000 --- a/libs/partners/couchbase/langchain_couchbase/chat_message_histories.py +++ /dev/null @@ -1,269 +0,0 @@ -"""Couchbase Chat Message History""" - -import logging -import time -import uuid -from datetime import timedelta -from typing import Any, Dict, List, Optional, Sequence - -from couchbase.cluster import Cluster -from langchain_core.chat_history import BaseChatMessageHistory -from langchain_core.messages import ( - BaseMessage, - message_to_dict, - messages_from_dict, -) - -logger = logging.getLogger(__name__) - -DEFAULT_SESSION_ID_KEY = "session_id" -DEFAULT_MESSAGE_KEY = "message" -DEFAULT_TS_KEY = "ts" -DEFAULT_INDEX_NAME = "LANGCHAIN_CHAT_HISTORY" -DEFAULT_BATCH_SIZE = 100 - - -def _validate_ttl(ttl: Optional[timedelta]) -> None: - """Validate the time to live""" - if not isinstance(ttl, timedelta): - raise ValueError(f"ttl should be of type timedelta but was {type(ttl)}.") - if ttl <= timedelta(seconds=0): - raise ValueError( - f"ttl must be greater than 0 but was {ttl.total_seconds()} seconds." - ) - - -class CouchbaseChatMessageHistory(BaseChatMessageHistory): - """Couchbase Chat Message History - Chat message history that uses Couchbase as the storage - """ - - def _check_bucket_exists(self) -> bool: - """Check if the bucket exists in the linked Couchbase cluster""" - bucket_manager = self._cluster.buckets() - try: - bucket_manager.get_bucket(self._bucket_name) - return True - except Exception: - return False - - def _check_scope_and_collection_exists(self) -> bool: - """Check if the scope and collection exists in the linked Couchbase bucket - Raises a ValueError if either is not found""" - scope_collection_map: Dict[str, Any] = {} - - # Get a list of all scopes in the bucket - for scope in self._bucket.collections().get_all_scopes(): - scope_collection_map[scope.name] = [] - - # Get a list of all the collections in the scope - for collection in scope.collections: - scope_collection_map[scope.name].append(collection.name) - - # Check if the scope exists - if self._scope_name not in scope_collection_map.keys(): - raise ValueError( - f"Scope {self._scope_name} not found in Couchbase " - f"bucket {self._bucket_name}" - ) - - # Check if the collection exists in the scope - if self._collection_name not in scope_collection_map[self._scope_name]: - raise ValueError( - f"Collection {self._collection_name} not found in scope " - f"{self._scope_name} in Couchbase bucket " - f"{self._bucket_name}" - ) - - return True - - def __init__( - self, - *, - cluster: Cluster, - bucket_name: str, - scope_name: str, - collection_name: str, - session_id: str, - session_id_key: str = DEFAULT_SESSION_ID_KEY, - message_key: str = DEFAULT_MESSAGE_KEY, - create_index: bool = True, - ttl: Optional[timedelta] = None, - ) -> None: - """Initialize the Couchbase Chat Message History - Args: - cluster (Cluster): couchbase cluster object with active connection. - bucket_name (str): name of the bucket to store documents in. - scope_name (str): name of the scope in bucket to store documents in. - collection_name (str): name of the collection in the scope to store - documents in. - session_id (str): value for the session used to associate messages from - a single chat session. It is stored as a field in the chat message. - session_id_key (str): name of the field to use for the session id. - Set to "session_id" by default. - message_key (str): name of the field to use for the messages - Set to "message" by default. - create_index (bool): create an index if True. Set to True by default. - ttl (timedelta): time to live for the documents in the collection. - When set, the documents are automatically deleted after the ttl expires. - """ - if not isinstance(cluster, Cluster): - raise ValueError( - f"cluster should be an instance of couchbase.Cluster, " - f"got {type(cluster)}" - ) - - self._cluster = cluster - - self._bucket_name = bucket_name - self._scope_name = scope_name - self._collection_name = collection_name - self._ttl = None - - # Check if the bucket exists - if not self._check_bucket_exists(): - raise ValueError( - f"Bucket {self._bucket_name} does not exist. " - " Please create the bucket before searching." - ) - - try: - self._bucket = self._cluster.bucket(self._bucket_name) - self._scope = self._bucket.scope(self._scope_name) - self._collection = self._scope.collection(self._collection_name) - except Exception as e: - raise ValueError( - "Error connecting to couchbase. " - "Please check the connection and credentials." - ) from e - - # Check if the scope and collection exists. Throws ValueError if they don't - try: - self._check_scope_and_collection_exists() - except Exception as e: - raise e - - self._session_id_key = session_id_key - self._message_key = message_key - self._create_index = create_index - self._session_id = session_id - self._ts_key = DEFAULT_TS_KEY - - if ttl is not None: - _validate_ttl(ttl) - self._ttl = ttl - - # Create an index if it does not exist if requested - if create_index: - index_fields = ( - f"({self._session_id_key}, {self._ts_key}, {self._message_key})" - ) - index_creation_query = ( - f"CREATE INDEX {DEFAULT_INDEX_NAME} IF NOT EXISTS ON " - + f"{self._collection_name}{index_fields} " - ) - - try: - self._scope.query(index_creation_query).execute() - except Exception as e: - logger.error("Error creating index: ", e) - - def add_message(self, message: BaseMessage) -> None: - """Add a message to the cache""" - # Generate a UUID for the document key - document_key = uuid.uuid4().hex - # get utc timestamp for ordering the messages - timestamp = time.time() - message_content = message_to_dict(message) - - try: - if self._ttl: - self._collection.insert( - document_key, - value={ - self._message_key: message_content, - self._session_id_key: self._session_id, - self._ts_key: timestamp, - }, - expiry=self._ttl, - ) - else: - self._collection.insert( - document_key, - value={ - self._message_key: message_content, - self._session_id_key: self._session_id, - self._ts_key: timestamp, - }, - ) - except Exception as e: - logger.error("Error adding message: ", e) - - def add_messages(self, messages: Sequence[BaseMessage]) -> None: - """Add messages to the cache in a batched manner""" - batch_size = DEFAULT_BATCH_SIZE - messages_to_insert = [] - for message in messages: - document_key = uuid.uuid4().hex - timestamp = time.time() - message_content = message_to_dict(message) - messages_to_insert.append( - { - document_key: { - self._message_key: message_content, - self._session_id_key: self._session_id, - self._ts_key: timestamp, - }, - } - ) - - # Add the messages to the cache in batches of batch_size - try: - for i in range(0, len(messages_to_insert), batch_size): - batch = messages_to_insert[i : i + batch_size] - # Convert list of dictionaries to a single dictionary to insert - insert_batch = {list(d.keys())[0]: list(d.values())[0] for d in batch} - if self._ttl: - self._collection.insert_multi(insert_batch, expiry=self._ttl) - else: - self._collection.insert_multi(insert_batch) - except Exception as e: - logger.error("Error adding messages: ", e) - - def clear(self) -> None: - """Clear the cache""" - # Delete all documents in the collection with the session_id - clear_query = ( - f"DELETE FROM `{self._collection_name}`" - + f"WHERE {self._session_id_key}=$session_id" - ) - try: - self._scope.query(clear_query, session_id=self._session_id).execute() - except Exception as e: - logger.error("Error clearing cache: ", e) - - @property - def messages(self) -> List[BaseMessage]: - """Get all messages in the cache associated with the session_id""" - fetch_query = ( - f"SELECT {self._message_key} FROM `{self._collection_name}` " - + f"where {self._session_id_key}=$session_id" - + f" ORDER BY {self._ts_key} ASC" - ) - message_items = [] - - try: - result = self._scope.query(fetch_query, session_id=self._session_id) - for document in result: - message_items.append(document[f"{self._message_key}"]) - except Exception as e: - logger.error("Error fetching messages: ", e) - - return messages_from_dict(message_items) - - @messages.setter - def messages(self, messages: List[BaseMessage]) -> None: - raise NotImplementedError( - "Direct assignment to 'messages' is not allowed." - " Use the 'add_messages' instead." - ) diff --git a/libs/partners/couchbase/langchain_couchbase/py.typed b/libs/partners/couchbase/langchain_couchbase/py.typed deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/libs/partners/couchbase/langchain_couchbase/vectorstores.py b/libs/partners/couchbase/langchain_couchbase/vectorstores.py deleted file mode 100644 index e76a206bbd3d7..0000000000000 --- a/libs/partners/couchbase/langchain_couchbase/vectorstores.py +++ /dev/null @@ -1,739 +0,0 @@ -"""Couchbase vector stores.""" - -from __future__ import annotations - -import uuid -from typing import ( - Any, - Dict, - Iterable, - List, - Optional, - Tuple, - Type, -) - -import couchbase.search as search -from couchbase.cluster import Cluster -from couchbase.exceptions import DocumentExistsException, DocumentNotFoundException -from couchbase.options import SearchOptions -from couchbase.vector_search import VectorQuery, VectorSearch -from langchain_core.documents import Document -from langchain_core.embeddings import Embeddings -from langchain_core.vectorstores import VectorStore - - -class CouchbaseVectorStore(VectorStore): - """__ModuleName__ vector store integration. - - Setup: - Install ``langchain-couchbase`` and head over to the Couchbase [website](https://cloud.couchbase.com) and create a new connection, with a bucket, collection, and search index. - - .. code-block:: bash - - pip install -U langchain-couchbase - - .. code-block:: python - - import getpass - - COUCHBASE_CONNECTION_STRING = getpass.getpass("Enter the connection string for the Couchbase cluster: ") - DB_USERNAME = getpass.getpass("Enter the username for the Couchbase cluster: ") - DB_PASSWORD = getpass.getpass("Enter the password for the Couchbase cluster: ") - - Key init args — indexing params: - embedding: Embeddings - Embedding function to use. - - Key init args — client params: - cluster: Cluster - Couchbase cluster object with active connection. - bucket_name: str - Name of the bucket to store documents in. - scope_name: str - Name of the scope in the bucket to store documents in. - collection_name: str - Name of the collection in the scope to store documents in. - index_name: str - Name of the Search index to use. - - Instantiate: - .. code-block:: python - - from datetime import timedelta - from langchain_openai import OpenAIEmbeddings - from couchbase.auth import PasswordAuthenticator - from couchbase.cluster import Cluster - from couchbase.options import ClusterOptions - - auth = PasswordAuthenticator(DB_USERNAME, DB_PASSWORD) - options = ClusterOptions(auth) - cluster = Cluster(COUCHBASE_CONNECTION_STRING, options) - - # Wait until the cluster is ready for use. - cluster.wait_until_ready(timedelta(seconds=5)) - - BUCKET_NAME = "langchain_bucket" - SCOPE_NAME = "_default" - COLLECTION_NAME = "default" - SEARCH_INDEX_NAME = "langchain-test-index" - - vector_store = CouchbaseVectorStore( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - embedding=embeddings, - index_name=SEARCH_INDEX_NAME, - ) - - Add Documents: - .. code-block:: python - - from langchain_core.documents import Document - - document_1 = Document(page_content="foo", metadata={"baz": "bar"}) - document_2 = Document(page_content="thud", metadata={"bar": "baz"}) - document_3 = Document(page_content="i will be deleted :(") - - documents = [document_1, document_2, document_3] - ids = ["1", "2", "3"] - vector_store.add_documents(documents=documents, ids=ids) - - Delete Documents: - .. code-block:: python - - vector_store.delete(ids=["3"]) - - # TODO: Fill out with example output. - Search: - .. code-block:: python - - results = vector_store.similarity_search(query="thud",k=1) - for doc in results: - print(f"* {doc.page_content} [{doc.metadata}]") - - .. code-block:: python - - # TODO: Example output - - # TODO: Fill out with relevant variables and example output. - Search with filter: - .. code-block:: python - - # TODO: Update filter to correct format - results = vector_store.similarity_search(query="thud",k=1,filter={"bar": "baz"}) - for doc in results: - print(f"* {doc.page_content} [{doc.metadata}]") - - .. code-block:: python - - # TODO: Example output - - # TODO: Fill out with example output. - Search with score: - .. code-block:: python - - results = vector_store.similarity_search_with_score(query="qux",k=1) - for doc, score in results: - print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]") - - .. code-block:: python - - # TODO: Example output - - # TODO: Fill out with example output. - Async: - .. code-block:: python - - # add documents - # await vector_store.aadd_documents(documents=documents, ids=ids) - - # delete documents - # await vector_store.adelete(ids=["3"]) - - # search - # results = vector_store.asimilarity_search(query="thud",k=1) - - # search with score - results = await vector_store.asimilarity_search_with_score(query="qux",k=1) - for doc,score in results: - print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]") - - .. code-block:: python - - # TODO: Example output - - # TODO: Fill out with example output. - Use as Retriever: - .. code-block:: python - - retriever = vector_store.as_retriever( - search_type="mmr", - search_kwargs={"k": 1, "fetch_k": 2, "lambda_mult": 0.5}, - ) - retriever.invoke("thud") - - .. code-block:: python - - # TODO: Example output - - """ # noqa: E501 - - # Default batch size - DEFAULT_BATCH_SIZE = 100 - _metadata_key = "metadata" - _default_text_key = "text" - _default_embedding_key = "embedding" - - def _check_bucket_exists(self) -> bool: - """Check if the bucket exists in the linked Couchbase cluster""" - bucket_manager = self._cluster.buckets() - try: - bucket_manager.get_bucket(self._bucket_name) - return True - except Exception: - return False - - def _check_scope_and_collection_exists(self) -> bool: - """Check if the scope and collection exists in the linked Couchbase bucket - Raises a ValueError if either is not found""" - scope_collection_map: Dict[str, Any] = {} - - # Get a list of all scopes in the bucket - for scope in self._bucket.collections().get_all_scopes(): - scope_collection_map[scope.name] = [] - - # Get a list of all the collections in the scope - for collection in scope.collections: - scope_collection_map[scope.name].append(collection.name) - - # Check if the scope exists - if self._scope_name not in scope_collection_map.keys(): - raise ValueError( - f"Scope {self._scope_name} not found in Couchbase " - f"bucket {self._bucket_name}" - ) - - # Check if the collection exists in the scope - if self._collection_name not in scope_collection_map[self._scope_name]: - raise ValueError( - f"Collection {self._collection_name} not found in scope " - f"{self._scope_name} in Couchbase bucket {self._bucket_name}" - ) - - return True - - def _check_index_exists(self) -> bool: - """Check if the Search index exists in the linked Couchbase cluster - Raises a ValueError if the index does not exist""" - if self._scoped_index: - all_indexes = [ - index.name for index in self._scope.search_indexes().get_all_indexes() - ] - if self._index_name not in all_indexes: - raise ValueError( - f"Index {self._index_name} does not exist. " - " Please create the index before searching." - ) - else: - all_indexes = [ - index.name for index in self._cluster.search_indexes().get_all_indexes() - ] - if self._index_name not in all_indexes: - raise ValueError( - f"Index {self._index_name} does not exist. " - " Please create the index before searching." - ) - - return True - - def __init__( - self, - cluster: Cluster, - bucket_name: str, - scope_name: str, - collection_name: str, - embedding: Embeddings, - index_name: str, - *, - text_key: Optional[str] = _default_text_key, - embedding_key: Optional[str] = _default_embedding_key, - scoped_index: bool = True, - ) -> None: - """ - Initialize the Couchbase Vector Store. - - Args: - - cluster (Cluster): couchbase cluster object with active connection. - bucket_name (str): name of bucket to store documents in. - scope_name (str): name of scope in the bucket to store documents in. - collection_name (str): name of collection in the scope to store documents in - embedding (Embeddings): embedding function to use. - index_name (str): name of the Search index to use. - text_key (optional[str]): key in document to use as text. - Set to text by default. - embedding_key (optional[str]): key in document to use for the embeddings. - Set to embedding by default. - scoped_index (optional[bool]): specify whether the index is a scoped index. - Set to True by default. - """ - if not isinstance(cluster, Cluster): - raise ValueError( - f"cluster should be an instance of couchbase.Cluster, " - f"got {type(cluster)}" - ) - - self._cluster = cluster - - if not embedding: - raise ValueError("Embeddings instance must be provided.") - - if not bucket_name: - raise ValueError("bucket_name must be provided.") - - if not scope_name: - raise ValueError("scope_name must be provided.") - - if not collection_name: - raise ValueError("collection_name must be provided.") - - if not index_name: - raise ValueError("index_name must be provided.") - - self._bucket_name = bucket_name - self._scope_name = scope_name - self._collection_name = collection_name - self._embedding_function = embedding - self._text_key = text_key - self._embedding_key = embedding_key - self._index_name = index_name - self._scoped_index = scoped_index - - # Check if the bucket exists - if not self._check_bucket_exists(): - raise ValueError( - f"Bucket {self._bucket_name} does not exist. " - " Please create the bucket before searching." - ) - - try: - self._bucket = self._cluster.bucket(self._bucket_name) - self._scope = self._bucket.scope(self._scope_name) - self._collection = self._scope.collection(self._collection_name) - except Exception as e: - raise ValueError( - "Error connecting to couchbase. " - "Please check the connection and credentials." - ) from e - - # Check if the scope and collection exists. Throws ValueError if they don't - try: - self._check_scope_and_collection_exists() - except Exception as e: - raise e - - # Check if the index exists. Throws ValueError if it doesn't - try: - self._check_index_exists() - except Exception as e: - raise e - - def add_texts( - self, - texts: Iterable[str], - metadatas: Optional[List[dict]] = None, - ids: Optional[List[str]] = None, - batch_size: Optional[int] = None, - **kwargs: Any, - ) -> List[str]: - """Run texts through the embeddings and persist in vectorstore. - - If the document IDs are passed, the existing documents (if any) will be - overwritten with the new ones. - - Args: - texts (Iterable[str]): Iterable of strings to add to the vectorstore. - metadatas (Optional[List[Dict]]): Optional list of metadatas associated - with the texts. - ids (Optional[List[str]]): Optional list of ids associated with the texts. - IDs have to be unique strings across the collection. - If it is not specified uuids are generated and used as ids. - batch_size (Optional[int]): Optional batch size for bulk insertions. - Default is 100. - - Returns: - List[str]:List of ids from adding the texts into the vectorstore. - """ - - if not batch_size: - batch_size = self.DEFAULT_BATCH_SIZE - doc_ids: List[str] = [] - - if ids is None: - ids = [uuid.uuid4().hex for _ in texts] - - if metadatas is None: - metadatas = [{} for _ in texts] - - # Check if TTL is provided - ttl = kwargs.get("ttl", None) - - embedded_texts = self._embedding_function.embed_documents(list(texts)) - - documents_to_insert = [ - { - id: { - self._text_key: text, - self._embedding_key: vector, - self._metadata_key: metadata, - } - for id, text, vector, metadata in zip( - ids, texts, embedded_texts, metadatas - ) - } - ] - - # Insert in batches - for i in range(0, len(documents_to_insert), batch_size): - batch = documents_to_insert[i : i + batch_size] - try: - # Insert with TTL if provided - if ttl: - result = self._collection.upsert_multi(batch[0], expiry=ttl) - else: - result = self._collection.upsert_multi(batch[0]) - if result.all_ok: - doc_ids.extend(batch[0].keys()) - except DocumentExistsException as e: - raise ValueError(f"Document already exists: {e}") - - return doc_ids - - def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> Optional[bool]: - """Delete documents from the vector store by ids. - - Args: - ids (List[str]): List of IDs of the documents to delete. - batch_size (Optional[int]): Optional batch size for bulk deletions. - - Returns: - bool: True if all the documents were deleted successfully, False otherwise. - - """ - - if ids is None: - raise ValueError("No document ids provided to delete.") - - batch_size = kwargs.get("batch_size", self.DEFAULT_BATCH_SIZE) - deletion_status = True - - # Delete in batches - for i in range(0, len(ids), batch_size): - batch = ids[i : i + batch_size] - try: - result = self._collection.remove_multi(batch) - except DocumentNotFoundException as e: - deletion_status = False - raise ValueError(f"Document not found: {e}") - - deletion_status &= result.all_ok - - return deletion_status - - @property - def embeddings(self) -> Embeddings: - """Return the query embedding object.""" - return self._embedding_function - - def _format_metadata(self, row_fields: Dict[str, Any]) -> Dict[str, Any]: - """Helper method to format the metadata from the Couchbase Search API. - Args: - row_fields (Dict[str, Any]): The fields to format. - - Returns: - Dict[str, Any]: The formatted metadata. - """ - metadata = {} - for key, value in row_fields.items(): - # Couchbase Search returns the metadata key with a prefix - # `metadata.` We remove it to get the original metadata key - if key.startswith(self._metadata_key): - new_key = key.split(self._metadata_key + ".")[-1] - metadata[new_key] = value - else: - metadata[key] = value - - return metadata - - def similarity_search( - self, - query: str, - k: int = 4, - search_options: Optional[Dict[str, Any]] = {}, - **kwargs: Any, - ) -> List[Document]: - """Return documents most similar to embedding vector with their scores. - - Args: - query (str): Query to look up for similar documents - k (int): Number of Documents to return. - Defaults to 4. - search_options (Optional[Dict[str, Any]]): Optional search options that are - passed to Couchbase search. - Defaults to empty dictionary - fields (Optional[List[str]]): Optional list of fields to include in the - metadata of results. Note that these need to be stored in the index. - If nothing is specified, defaults to all the fields stored in the index. - - Returns: - List of Documents most similar to the query. - """ - query_embedding = self.embeddings.embed_query(query) - docs_with_scores = self.similarity_search_with_score_by_vector( - query_embedding, k, search_options, **kwargs - ) - return [doc for doc, _ in docs_with_scores] - - def similarity_search_with_score_by_vector( - self, - embedding: List[float], - k: int = 4, - search_options: Optional[Dict[str, Any]] = {}, - **kwargs: Any, - ) -> List[Tuple[Document, float]]: - """Return docs most similar to embedding vector with their scores. - - Args: - embedding (List[float]): Embedding vector to look up documents similar to. - k (int): Number of Documents to return. - Defaults to 4. - search_options (Optional[Dict[str, Any]]): Optional search options that are - passed to Couchbase search. - Defaults to empty dictionary. - fields (Optional[List[str]]): Optional list of fields to include in the - metadata of results. Note that these need to be stored in the index. - If nothing is specified, defaults to all the fields stored in the index. - - Returns: - List of (Document, score) that are the most similar to the query vector. - """ - - fields = kwargs.get("fields", ["*"]) - - # Document text field needs to be returned from the search - if fields != ["*"] and self._text_key not in fields: - fields.append(self._text_key) - - search_req = search.SearchRequest.create( - VectorSearch.from_vector_query( - VectorQuery( - self._embedding_key, - embedding, - k, - ) - ) - ) - try: - if self._scoped_index: - search_iter = self._scope.search( - self._index_name, - search_req, - SearchOptions( - limit=k, - fields=fields, - raw=search_options, - ), - ) - - else: - search_iter = self._cluster.search( - self._index_name, - search_req, - SearchOptions(limit=k, fields=fields, raw=search_options), - ) - - docs_with_score = [] - - # Parse the results - for row in search_iter.rows(): - text = row.fields.pop(self._text_key, "") - id = row.id - - # Format the metadata from Couchbase - metadata = self._format_metadata(row.fields) - - score = row.score - doc = Document(id=id, page_content=text, metadata=metadata) - docs_with_score.append((doc, score)) - - except Exception as e: - raise ValueError(f"Search failed with error: {e}") - - return docs_with_score - - def similarity_search_with_score( - self, - query: str, - k: int = 4, - search_options: Optional[Dict[str, Any]] = {}, - **kwargs: Any, - ) -> List[Tuple[Document, float]]: - """Return documents that are most similar to the query with their scores. - - Args: - query (str): Query to look up for similar documents - k (int): Number of Documents to return. - Defaults to 4. - search_options (Optional[Dict[str, Any]]): Optional search options that are - passed to Couchbase search. - Defaults to empty dictionary. - fields (Optional[List[str]]): Optional list of fields to include in the - metadata of results. Note that these need to be stored in the index. - If nothing is specified, defaults to text and metadata fields. - - Returns: - List of (Document, score) that are most similar to the query. - """ - query_embedding = self.embeddings.embed_query(query) - docs_with_score = self.similarity_search_with_score_by_vector( - query_embedding, k, search_options, **kwargs - ) - return docs_with_score - - def similarity_search_by_vector( - self, - embedding: List[float], - k: int = 4, - search_options: Optional[Dict[str, Any]] = {}, - **kwargs: Any, - ) -> List[Document]: - """Return documents that are most similar to the vector embedding. - - Args: - embedding (List[float]): Embedding to look up documents similar to. - k (int): Number of Documents to return. - Defaults to 4. - search_options (Optional[Dict[str, Any]]): Optional search options that are - passed to Couchbase search. - Defaults to empty dictionary. - fields (Optional[List[str]]): Optional list of fields to include in the - metadata of results. Note that these need to be stored in the index. - If nothing is specified, defaults to document text and metadata fields. - - Returns: - List of Documents most similar to the query. - """ - docs_with_score = self.similarity_search_with_score_by_vector( - embedding, k, search_options, **kwargs - ) - return [doc for doc, _ in docs_with_score] - - @classmethod - def _from_kwargs( - cls: Type[CouchbaseVectorStore], - embedding: Embeddings, - **kwargs: Any, - ) -> CouchbaseVectorStore: - """Initialize the Couchbase vector store from keyword arguments for the - vector store. - - Args: - embedding: Embedding object to use to embed text. - **kwargs: Keyword arguments to initialize the vector store with. - Accepted arguments are: - - cluster - - bucket_name - - scope_name - - collection_name - - index_name - - text_key - - embedding_key - - scoped_index - - """ - cluster = kwargs.get("cluster", None) - bucket_name = kwargs.get("bucket_name", None) - scope_name = kwargs.get("scope_name", None) - collection_name = kwargs.get("collection_name", None) - index_name = kwargs.get("index_name", None) - text_key = kwargs.get("text_key", cls._default_text_key) - embedding_key = kwargs.get("embedding_key", cls._default_embedding_key) - scoped_index = kwargs.get("scoped_index", True) - - return cls( - embedding=embedding, - cluster=cluster, - bucket_name=bucket_name, - scope_name=scope_name, - collection_name=collection_name, - index_name=index_name, - text_key=text_key, - embedding_key=embedding_key, - scoped_index=scoped_index, - ) - - @classmethod - def from_texts( - cls: Type[CouchbaseVectorStore], - texts: List[str], - embedding: Embeddings, - metadatas: Optional[List[dict]] = None, - **kwargs: Any, - ) -> CouchbaseVectorStore: - """Construct a Couchbase vector store from a list of texts. - - Example: - .. code-block:: python - - from langchain_couchbase import CouchbaseVectorStore - from langchain_openai import OpenAIEmbeddings - - from couchbase.cluster import Cluster - from couchbase.auth import PasswordAuthenticator - from couchbase.options import ClusterOptions - from datetime import timedelta - - auth = PasswordAuthenticator(username, password) - options = ClusterOptions(auth) - connect_string = "couchbases://localhost" - cluster = Cluster(connect_string, options) - - # Wait until the cluster is ready for use. - cluster.wait_until_ready(timedelta(seconds=5)) - - embeddings = OpenAIEmbeddings() - - texts = ["hello", "world"] - - vectorstore = CouchbaseVectorStore.from_texts( - texts, - embedding=embeddings, - cluster=cluster, - bucket_name="", - scope_name="", - collection_name="", - index_name="vector-index", - ) - - Args: - texts (List[str]): list of texts to add to the vector store. - embedding (Embeddings): embedding function to use. - metadatas (optional[List[Dict]): list of metadatas to add to documents. - **kwargs: Keyword arguments used to initialize the vector store with and/or - passed to `add_texts` method. Check the constructor and/or `add_texts` - for the list of accepted arguments. - - Returns: - A Couchbase vector store. - - """ - vector_store = cls._from_kwargs(embedding, **kwargs) - batch_size = kwargs.get("batch_size", vector_store.DEFAULT_BATCH_SIZE) - ids = kwargs.get("ids", None) - vector_store.add_texts( - texts, metadatas=metadatas, ids=ids, batch_size=batch_size - ) - - return vector_store diff --git a/libs/partners/couchbase/poetry.lock b/libs/partners/couchbase/poetry.lock deleted file mode 100644 index 328d7abc51d7c..0000000000000 --- a/libs/partners/couchbase/poetry.lock +++ /dev/null @@ -1,1790 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. - -[[package]] -name = "aiohappyeyeballs" -version = "2.4.3" -description = "Happy Eyeballs for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, -] - -[[package]] -name = "aiohttp" -version = "3.11.7" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "aiohttp-3.11.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8bedb1f6cb919af3b6353921c71281b1491f948ca64408871465d889b4ee1b66"}, - {file = "aiohttp-3.11.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f5022504adab881e2d801a88b748ea63f2a9d130e0b2c430824682a96f6534be"}, - {file = "aiohttp-3.11.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e22d1721c978a6494adc824e0916f9d187fa57baeda34b55140315fa2f740184"}, - {file = "aiohttp-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e993676c71288618eb07e20622572b1250d8713e7e00ab3aabae28cb70f3640d"}, - {file = "aiohttp-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e13a05db87d3b241c186d0936808d0e4e12decc267c617d54e9c643807e968b6"}, - {file = "aiohttp-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ba8d043fed7ffa117024d7ba66fdea011c0e7602327c6d73cacaea38abe4491"}, - {file = "aiohttp-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda3ed0a7869d2fa16aa41f9961ade73aa2c2e3b2fcb0a352524e7b744881889"}, - {file = "aiohttp-3.11.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43bfd25113c1e98aec6c70e26d5f4331efbf4aa9037ba9ad88f090853bf64d7f"}, - {file = "aiohttp-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3dd3e7e7c9ef3e7214f014f1ae260892286647b3cf7c7f1b644a568fd410f8ca"}, - {file = "aiohttp-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:78c657ece7a73b976905ab9ec8be9ef2df12ed8984c24598a1791c58ce3b4ce4"}, - {file = "aiohttp-3.11.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:db70a47987e34494b451a334605bee57a126fe8d290511349e86810b4be53b01"}, - {file = "aiohttp-3.11.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9e67531370a3b07e49b280c1f8c2df67985c790ad2834d1b288a2f13cd341c5f"}, - {file = "aiohttp-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9202f184cc0582b1db15056f2225ab4c1e3dac4d9ade50dd0613ac3c46352ac2"}, - {file = "aiohttp-3.11.7-cp310-cp310-win32.whl", hash = "sha256:2257bdd5cf54a4039a4337162cd8048f05a724380a2283df34620f55d4e29341"}, - {file = "aiohttp-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:b7215bf2b53bc6cb35808149980c2ae80a4ae4e273890ac85459c014d5aa60ac"}, - {file = "aiohttp-3.11.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cea52d11e02123f125f9055dfe0ccf1c3857225fb879e4a944fae12989e2aef2"}, - {file = "aiohttp-3.11.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ce18f703b7298e7f7633efd6a90138d99a3f9a656cb52c1201e76cb5d79cf08"}, - {file = "aiohttp-3.11.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:670847ee6aeb3a569cd7cdfbe0c3bec1d44828bbfbe78c5d305f7f804870ef9e"}, - {file = "aiohttp-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dda726f89bfa5c465ba45b76515135a3ece0088dfa2da49b8bb278f3bdeea12"}, - {file = "aiohttp-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25b74a811dba37c7ea6a14d99eb9402d89c8d739d50748a75f3cf994cf19c43"}, - {file = "aiohttp-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5522ee72f95661e79db691310290c4618b86dff2d9b90baedf343fd7a08bf79"}, - {file = "aiohttp-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fbf41a6bbc319a7816ae0f0177c265b62f2a59ad301a0e49b395746eb2a9884"}, - {file = "aiohttp-3.11.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59ee1925b5a5efdf6c4e7be51deee93984d0ac14a6897bd521b498b9916f1544"}, - {file = "aiohttp-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24054fce8c6d6f33a3e35d1c603ef1b91bbcba73e3f04a22b4f2f27dac59b347"}, - {file = "aiohttp-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:351849aca2c6f814575c1a485c01c17a4240413f960df1bf9f5deb0003c61a53"}, - {file = "aiohttp-3.11.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:12724f3a211fa243570e601f65a8831372caf1a149d2f1859f68479f07efec3d"}, - {file = "aiohttp-3.11.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7ea4490360b605804bea8173d2d086b6c379d6bb22ac434de605a9cbce006e7d"}, - {file = "aiohttp-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e0bf378db07df0a713a1e32381a1b277e62ad106d0dbe17b5479e76ec706d720"}, - {file = "aiohttp-3.11.7-cp311-cp311-win32.whl", hash = "sha256:cd8d62cab363dfe713067027a5adb4907515861f1e4ce63e7be810b83668b847"}, - {file = "aiohttp-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:bf0e6cce113596377cadda4e3ac5fb89f095bd492226e46d91b4baef1dd16f60"}, - {file = "aiohttp-3.11.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4bb7493c3e3a36d3012b8564bd0e2783259ddd7ef3a81a74f0dbfa000fce48b7"}, - {file = "aiohttp-3.11.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e143b0ef9cb1a2b4f74f56d4fbe50caa7c2bb93390aff52f9398d21d89bc73ea"}, - {file = "aiohttp-3.11.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7c58a240260822dc07f6ae32a0293dd5bccd618bb2d0f36d51c5dbd526f89c0"}, - {file = "aiohttp-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d20cfe63a1c135d26bde8c1d0ea46fd1200884afbc523466d2f1cf517d1fe33"}, - {file = "aiohttp-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12e4d45847a174f77b2b9919719203769f220058f642b08504cf8b1cf185dacf"}, - {file = "aiohttp-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf4efa2d01f697a7dbd0509891a286a4af0d86902fc594e20e3b1712c28c0106"}, - {file = "aiohttp-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee6a4cdcbf54b8083dc9723cdf5f41f722c00db40ccf9ec2616e27869151129"}, - {file = "aiohttp-3.11.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6095aaf852c34f42e1bd0cf0dc32d1e4b48a90bfb5054abdbb9d64b36acadcb"}, - {file = "aiohttp-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1cf03d27885f8c5ebf3993a220cc84fc66375e1e6e812731f51aab2b2748f4a6"}, - {file = "aiohttp-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1a17f6a230f81eb53282503823f59d61dff14fb2a93847bf0399dc8e87817307"}, - {file = "aiohttp-3.11.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:481f10a1a45c5f4c4a578bbd74cff22eb64460a6549819242a87a80788461fba"}, - {file = "aiohttp-3.11.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:db37248535d1ae40735d15bdf26ad43be19e3d93ab3f3dad8507eb0f85bb8124"}, - {file = "aiohttp-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d18a8b44ec8502a7fde91446cd9c9b95ce7c49f1eacc1fb2358b8907d4369fd"}, - {file = "aiohttp-3.11.7-cp312-cp312-win32.whl", hash = "sha256:3d1c9c15d3999107cbb9b2d76ca6172e6710a12fda22434ee8bd3f432b7b17e8"}, - {file = "aiohttp-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:018f1b04883a12e77e7fc161934c0f298865d3a484aea536a6a2ca8d909f0ba0"}, - {file = "aiohttp-3.11.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:241a6ca732d2766836d62c58c49ca7a93d08251daef0c1e3c850df1d1ca0cbc4"}, - {file = "aiohttp-3.11.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa3705a8d14de39898da0fbad920b2a37b7547c3afd2a18b9b81f0223b7d0f68"}, - {file = "aiohttp-3.11.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9acfc7f652b31853eed3b92095b0acf06fd5597eeea42e939bd23a17137679d5"}, - {file = "aiohttp-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcefcf2915a2dbdbce37e2fc1622129a1918abfe3d06721ce9f6cdac9b6d2eaa"}, - {file = "aiohttp-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1f6490dd1862af5aae6cfcf2a274bffa9a5b32a8f5acb519a7ecf5a99a88866"}, - {file = "aiohttp-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac5462582d6561c1c1708853a9faf612ff4e5ea5e679e99be36143d6eabd8e"}, - {file = "aiohttp-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1a6309005acc4b2bcc577ba3b9169fea52638709ffacbd071f3503264620da"}, - {file = "aiohttp-3.11.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b973cce96793725ef63eb449adfb74f99c043c718acb76e0d2a447ae369962"}, - {file = "aiohttp-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ce91a24aac80de6be8512fb1c4838a9881aa713f44f4e91dd7bb3b34061b497d"}, - {file = "aiohttp-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:875f7100ce0e74af51d4139495eec4025affa1a605280f23990b6434b81df1bd"}, - {file = "aiohttp-3.11.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c171fc35d3174bbf4787381716564042a4cbc008824d8195eede3d9b938e29a8"}, - {file = "aiohttp-3.11.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ee9afa1b0d2293c46954f47f33e150798ad68b78925e3710044e0d67a9487791"}, - {file = "aiohttp-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8360c7cc620abb320e1b8d603c39095101391a82b1d0be05fb2225471c9c5c52"}, - {file = "aiohttp-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7a9318da4b4ada9a67c1dd84d1c0834123081e746bee311a16bb449f363d965e"}, - {file = "aiohttp-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:fc6da202068e0a268e298d7cd09b6e9f3997736cd9b060e2750963754552a0a9"}, - {file = "aiohttp-3.11.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:17829f37c0d31d89aa6b8b010475a10233774771f9b6dc2cc352ea4f8ce95d9a"}, - {file = "aiohttp-3.11.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d6177077a31b1aecfc3c9070bd2f11419dbb4a70f30f4c65b124714f525c2e48"}, - {file = "aiohttp-3.11.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:badda65ac99555791eed75e234afb94686ed2317670c68bff8a4498acdaee935"}, - {file = "aiohttp-3.11.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0de6466b9d742b4ee56fe1b2440706e225eb48c77c63152b1584864a236e7a50"}, - {file = "aiohttp-3.11.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04b0cc74d5a882c9dacaeeccc1444f0233212b6f5be8bc90833feef1e1ce14b9"}, - {file = "aiohttp-3.11.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c7af3e50e5903d21d7b935aceed901cc2475463bc16ddd5587653548661fdb"}, - {file = "aiohttp-3.11.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c63f898f683d1379b9be5afc3dd139e20b30b0b1e0bf69a3fc3681f364cf1629"}, - {file = "aiohttp-3.11.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdadc3f6a32d6eca45f9a900a254757fd7855dfb2d8f8dcf0e88f0fae3ff8eb1"}, - {file = "aiohttp-3.11.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d329300fb23e14ed1f8c6d688dfd867d1dcc3b1d7cd49b7f8c5b44e797ce0932"}, - {file = "aiohttp-3.11.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5578cf40440eafcb054cf859964bc120ab52ebe0e0562d2b898126d868749629"}, - {file = "aiohttp-3.11.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7b2f8107a3c329789f3c00b2daad0e35f548d0a55cda6291579136622099a46e"}, - {file = "aiohttp-3.11.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:43dd89a6194f6ab02a3fe36b09e42e2df19c211fc2050ce37374d96f39604997"}, - {file = "aiohttp-3.11.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d2fa6fc7cc865d26ff42480ac9b52b8c9b7da30a10a6442a9cdf429de840e949"}, - {file = "aiohttp-3.11.7-cp39-cp39-win32.whl", hash = "sha256:a7d9a606355655617fee25dd7e54d3af50804d002f1fd3118dd6312d26692d70"}, - {file = "aiohttp-3.11.7-cp39-cp39-win_amd64.whl", hash = "sha256:53c921b58fdc6485d6b2603e0132bb01cd59b8f0620ffc0907f525e0ba071687"}, - {file = "aiohttp-3.11.7.tar.gz", hash = "sha256:01a8aca4af3da85cea5c90141d23f4b0eee3cbecfd33b029a45a80f28c66c668"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.3.0" -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -propcache = ">=0.2.0" -yarl = ">=1.17.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.6.2.post1" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -files = [ - {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, - {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "attrs" -version = "24.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, -] - -[package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "codespell" -version = "2.3.0" -description = "Codespell" -optional = false -python-versions = ">=3.8" -files = [ - {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, - {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, -] - -[package.extras] -dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] -hard-encoding-detection = ["chardet"] -toml = ["tomli"] -types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "couchbase" -version = "4.3.4" -description = "Python Client for Couchbase" -optional = false -python-versions = ">=3.7" -files = [ - {file = "couchbase-4.3.4-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:395e7b05495132a071dce5cdd84a3ec6e803205875f8ee22e85a89a16bb1b5f4"}, - {file = "couchbase-4.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:263a18307d1f1a141b93ae370b19843b1160dd702559152aea19dd08768f59f5"}, - {file = "couchbase-4.3.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16751d4f3b0fe49666515ebed7967e8f38ec3862b329f773f88252acfd7c2b1f"}, - {file = "couchbase-4.3.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4dcc7eb9f57825c0097785d1c042e146908d2883f5e733d4ca07ac548bb532a2"}, - {file = "couchbase-4.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42f16ca2ec636db9ecacd3b97db85c923be8374eaae2fe097124d8eb92b4293f"}, - {file = "couchbase-4.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:8eecc9cdead68efe4119ebe41b065dadf83bc1653ec56470800c5093e378cacc"}, - {file = "couchbase-4.3.4-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1a6d6c4542e4ffe223960553e057bc175cfcee3fe274f63551e9d90d7c2435c5"}, - {file = "couchbase-4.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae44db4ce78b691028075fc54beec2dc1a59829f53a2b282f9a8b3ea6b71ad22"}, - {file = "couchbase-4.3.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a175f1e447b9aeb7ab5aab66350261be28ad7d9a07fff9c7fe48c55828133ec3"}, - {file = "couchbase-4.3.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb0170d5e7766593d47292c14a782e201f0167175f0e60cd7ba3b9acd75e349"}, - {file = "couchbase-4.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3f7d9e0492727b8560d36f5cb45c2a6ec9507dd2120ddd6313fd21e04cfc2ab9"}, - {file = "couchbase-4.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:f32e9d87e5157b86af5de85200cab433690789551d2bda1b8e7a25bf2680d511"}, - {file = "couchbase-4.3.4-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:395afab875aa3d293429cebc080cc12ac6e32c665275740d5a8445c688ad84ce"}, - {file = "couchbase-4.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:852ff1e36668a9b0e0e4dc015df06e3a663bd5e0301a52c25b724969073a1a11"}, - {file = "couchbase-4.3.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79ab95992829de574e23588ce35fc14ab6f8a5fd378c046522a678b7583a9b29"}, - {file = "couchbase-4.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88827951b4132b89b6f37f1f2706b1e2f04870825c420e931c3caa770fc4a4e8"}, - {file = "couchbase-4.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d8f88c731d0d28132a992978aae5e1140a71276cc528ecc2ed408b2e386d1183"}, - {file = "couchbase-4.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:fb137358e249c752dbecb44393769696c07fd069eb976b2a9890ddd457d35fcb"}, - {file = "couchbase-4.3.4-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d3f84932dd2d26a06048fe0858be21f5c6907a304ce59d673d56691e6eda7626"}, - {file = "couchbase-4.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f0975f9efeba9c425c2c73e5c3b6f3b4041cb61e1c5c0240c581454b0fc222fe"}, - {file = "couchbase-4.3.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:43609d64306ac8be7c396c0395a140c8f6b5bbab889e4b943f1b0dd500e34568"}, - {file = "couchbase-4.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8a549126875e38a79f7f7d97094a482b3fd446c20266ddb5c274d6398be8477"}, - {file = "couchbase-4.3.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c1adc3c7cf411f1c61e2f9b4454719d25f7229594280d7dedc7a8c9c2da8189f"}, - {file = "couchbase-4.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:9b34b9599b29c2366e2943309c45c0666956e458848eb9b88a43a765afc8728c"}, - {file = "couchbase-4.3.4-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f9b9b5523fbc89189119eceea170c329cf02115e1eba59818faefb594b729520"}, - {file = "couchbase-4.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2cb01a0b567694a12abdb01f73392cf64cbc881e496e70b32f05f36ac50ca0f"}, - {file = "couchbase-4.3.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:31eb077c2cd9694b933a8a18836c117f6682a220b33a767b3379934b540e6e1c"}, - {file = "couchbase-4.3.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ad97d9467485667f8ba2b644c5823bb53fb1799dca5a29b671258d6af719ca0"}, - {file = "couchbase-4.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:51f50dd684e9894d5c8059ee6da5e9bc6e1a47ab3be103a3b299e7d01de02bab"}, - {file = "couchbase-4.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:1059b4358d1f1b69812f114d0c5a547f830ab9fb24bcd5076a05ceb4788adee1"}, - {file = "couchbase-4.3.4.tar.gz", hash = "sha256:f195958606cf3a255fd96646ca3dd7e2ddcecf3664b3883826c7b89ef680088e"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "frozenlist" -version = "1.5.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, -] - -[[package]] -name = "greenlet" -version = "3.1.1" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, - {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, - {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, - {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, - {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, - {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, - {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, - {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, - {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, - {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, - {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, - {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, - {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, - {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, - {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, - {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, - {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.7" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.27.2" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "langchain" -version = "0.3.8" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -files = [ - {file = "langchain-0.3.8-py3-none-any.whl", hash = "sha256:5cae404da30bf6730639a9ad85d3bf4fbb350c0038e5a0b81890e5883b4cff5c"}, - {file = "langchain-0.3.8.tar.gz", hash = "sha256:1cbbf7379b5b2f11b751fc527016f29ee5fe8a2697d166b52b7b5c63fc9702f9"}, -] - -[package.dependencies] -aiohttp = ">=3.8.3,<4.0.0" -async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.3.21,<0.4.0" -langchain-text-splitters = ">=0.3.0,<0.4.0" -langsmith = ">=0.1.17,<0.2.0" -numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, - {version = ">=1.26.2,<2", markers = "python_version >= \"3.12\""}, -] -pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" - -[[package]] -name = "langchain-core" -version = "0.3.21" -description = "Building applications with LLMs through composability" -optional = false -python-versions = ">=3.9,<4.0" -files = [] -develop = true - -[package.dependencies] -jsonpatch = "^1.33" -langsmith = "^0.1.125" -packaging = ">=23.2,<25" -pydantic = [ - {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -PyYAML = ">=5.3" -tenacity = ">=8.1.0,!=8.4.0,<10.0.0" -typing-extensions = ">=4.7" - -[package.source] -type = "directory" -url = "../../core" - -[[package]] -name = "langchain-text-splitters" -version = "0.3.2" -description = "LangChain text splitting utilities" -optional = false -python-versions = "<4.0,>=3.9" -files = [ - {file = "langchain_text_splitters-0.3.2-py3-none-any.whl", hash = "sha256:0db28c53f41d1bc024cdb3b1646741f6d46d5371e90f31e7e7c9fbe75d01c726"}, - {file = "langchain_text_splitters-0.3.2.tar.gz", hash = "sha256:81e6515d9901d6dd8e35fb31ccd4f30f76d44b771890c789dc835ef9f16204df"}, -] - -[package.dependencies] -langchain-core = ">=0.3.15,<0.4.0" - -[[package]] -name = "langsmith" -version = "0.1.146" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langsmith-0.1.146-py3-none-any.whl", hash = "sha256:9d062222f1a32c9b047dab0149b24958f988989cd8d4a5f9139ff959a51e59d8"}, - {file = "langsmith-0.1.146.tar.gz", hash = "sha256:ead8b0b9d5b6cd3ac42937ec48bdf09d4afe7ca1bba22dc05eb65591a18106f8"}, -] - -[package.dependencies] -httpx = ">=0.23.0,<1" -orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} -pydantic = [ - {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -requests = ">=2,<3" -requests-toolbelt = ">=1.0.0,<2.0.0" - -[[package]] -name = "multidict" -version = "6.1.0" -description = "multidict implementation" -optional = false -python-versions = ">=3.8" -files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "mypy" -version = "1.13.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "orjson" -version = "3.10.12" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, - {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, - {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, - {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, - {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, - {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, - {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, - {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, - {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, - {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, - {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, - {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, - {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, - {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, - {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, - {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, - {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, - {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, - {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, -] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "propcache" -version = "0.2.0" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.8" -files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, -] - -[[package]] -name = "pydantic" -version = "2.10.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, - {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.27.1" -typing-extensions = ">=4.12.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.27.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, - {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, - {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, - {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, - {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, - {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, - {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, - {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, - {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, - {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, - {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, - {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, - {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, - {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, - {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, - {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, - {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.8" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, - {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-socket" -version = "0.7.0" -description = "Pytest Plugin to disable socket calls during tests" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "pytest_socket-0.7.0-py3-none-any.whl", hash = "sha256:7e0f4642177d55d317bbd58fc68c6bd9048d6eadb2d46a89307fa9221336ce45"}, - {file = "pytest_socket-0.7.0.tar.gz", hash = "sha256:71ab048cbbcb085c15a4423b73b619a8b35d6a307f46f78ea46be51b1b7e11b3"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -description = "A utility belt for advanced users of python-requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, - {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, -] - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "ruff" -version = "0.5.7" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, - {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, - {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"}, - {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"}, - {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"}, - {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"}, - {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.36" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, - {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, - {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "syrupy" -version = "4.8.0" -description = "Pytest Snapshot Test Utility" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "syrupy-4.8.0-py3-none-any.whl", hash = "sha256:544f4ec6306f4b1c460fdab48fd60b2c7fe54a6c0a8243aeea15f9ad9c638c3f"}, - {file = "syrupy-4.8.0.tar.gz", hash = "sha256:648f0e9303aaa8387c8365d7314784c09a6bab0a407455c6a01d6a4f5c6a8ede"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9.0.0" - -[[package]] -name = "tenacity" -version = "9.0.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, - {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "tomli" -version = "2.1.0" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "yarl" -version = "1.18.0" -description = "Yet another URL library" -optional = false -python-versions = ">=3.9" -files = [ - {file = "yarl-1.18.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:074fee89caab89a97e18ef5f29060ef61ba3cae6cd77673acc54bfdd3214b7b7"}, - {file = "yarl-1.18.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b026cf2c32daf48d90c0c4e406815c3f8f4cfe0c6dfccb094a9add1ff6a0e41a"}, - {file = "yarl-1.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae38bd86eae3ba3d2ce5636cc9e23c80c9db2e9cb557e40b98153ed102b5a736"}, - {file = "yarl-1.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:685cc37f3f307c6a8e879986c6d85328f4c637f002e219f50e2ef66f7e062c1d"}, - {file = "yarl-1.18.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8254dbfce84ee5d1e81051ee7a0f1536c108ba294c0fdb5933476398df0654f3"}, - {file = "yarl-1.18.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20de4a8b04de70c49698dc2390b7fd2d18d424d3b876371f9b775e2b462d4b41"}, - {file = "yarl-1.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0a2074a37285570d54b55820687de3d2f2b9ecf1b714e482e48c9e7c0402038"}, - {file = "yarl-1.18.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f576ed278860df2721a5d57da3381040176ef1d07def9688a385c8330db61a1"}, - {file = "yarl-1.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3a3709450a574d61be6ac53d582496014342ea34876af8dc17cc16da32826c9a"}, - {file = "yarl-1.18.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bd80ed29761490c622edde5dd70537ca8c992c2952eb62ed46984f8eff66d6e8"}, - {file = "yarl-1.18.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:32141e13a1d5a48525e519c9197d3f4d9744d818d5c7d6547524cc9eccc8971e"}, - {file = "yarl-1.18.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8b8d3e4e014fb4274f1c5bf61511d2199e263909fb0b8bda2a7428b0894e8dc6"}, - {file = "yarl-1.18.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:701bb4a8f4de191c8c0cc9a1e6d5142f4df880e9d1210e333b829ca9425570ed"}, - {file = "yarl-1.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a45d94075ac0647621eaaf693c8751813a3eccac455d423f473ffed38c8ac5c9"}, - {file = "yarl-1.18.0-cp310-cp310-win32.whl", hash = "sha256:34176bfb082add67cb2a20abd85854165540891147f88b687a5ed0dc225750a0"}, - {file = "yarl-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:73553bbeea7d6ec88c08ad8027f4e992798f0abc459361bf06641c71972794dc"}, - {file = "yarl-1.18.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b8e8c516dc4e1a51d86ac975b0350735007e554c962281c432eaa5822aa9765c"}, - {file = "yarl-1.18.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e6b4466714a73f5251d84b471475850954f1fa6acce4d3f404da1d55d644c34"}, - {file = "yarl-1.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c893f8c1a6d48b25961e00922724732d00b39de8bb0b451307482dc87bddcd74"}, - {file = "yarl-1.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13aaf2bdbc8c86ddce48626b15f4987f22e80d898818d735b20bd58f17292ee8"}, - {file = "yarl-1.18.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd21c0128e301851de51bc607b0a6da50e82dc34e9601f4b508d08cc89ee7929"}, - {file = "yarl-1.18.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205de377bd23365cd85562c9c6c33844050a93661640fda38e0567d2826b50df"}, - {file = "yarl-1.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed69af4fe2a0949b1ea1d012bf065c77b4c7822bad4737f17807af2adb15a73c"}, - {file = "yarl-1.18.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e1c18890091aa3cc8a77967943476b729dc2016f4cfe11e45d89b12519d4a93"}, - {file = "yarl-1.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:91b8fb9427e33f83ca2ba9501221ffaac1ecf0407f758c4d2f283c523da185ee"}, - {file = "yarl-1.18.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:536a7a8a53b75b2e98ff96edb2dfb91a26b81c4fed82782035767db5a465be46"}, - {file = "yarl-1.18.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a64619a9c47c25582190af38e9eb382279ad42e1f06034f14d794670796016c0"}, - {file = "yarl-1.18.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c73a6bbc97ba1b5a0c3c992ae93d721c395bdbb120492759b94cc1ac71bc6350"}, - {file = "yarl-1.18.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a173401d7821a2a81c7b47d4e7d5c4021375a1441af0c58611c1957445055056"}, - {file = "yarl-1.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7520e799b1f84e095cce919bd6c23c9d49472deeef25fe1ef960b04cca51c3fc"}, - {file = "yarl-1.18.0-cp311-cp311-win32.whl", hash = "sha256:c4cb992d8090d5ae5f7afa6754d7211c578be0c45f54d3d94f7781c495d56716"}, - {file = "yarl-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:52c136f348605974c9b1c878addd6b7a60e3bf2245833e370862009b86fa4689"}, - {file = "yarl-1.18.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1ece25e2251c28bab737bdf0519c88189b3dd9492dc086a1d77336d940c28ced"}, - {file = "yarl-1.18.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:454902dc1830d935c90b5b53c863ba2a98dcde0fbaa31ca2ed1ad33b2a7171c6"}, - {file = "yarl-1.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01be8688fc211dc237e628fcc209dda412d35de7642453059a0553747018d075"}, - {file = "yarl-1.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d26f1fa9fa2167bb238f6f4b20218eb4e88dd3ef21bb8f97439fa6b5313e30d"}, - {file = "yarl-1.18.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b234a4a9248a9f000b7a5dfe84b8cb6210ee5120ae70eb72a4dcbdb4c528f72f"}, - {file = "yarl-1.18.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe94d1de77c4cd8caff1bd5480e22342dbd54c93929f5943495d9c1e8abe9f42"}, - {file = "yarl-1.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b4c90c5363c6b0a54188122b61edb919c2cd1119684999d08cd5e538813a28e"}, - {file = "yarl-1.18.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a98ecadc5a241c9ba06de08127ee4796e1009555efd791bac514207862b43d"}, - {file = "yarl-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9106025c7f261f9f5144f9aa7681d43867eed06349a7cfb297a1bc804de2f0d1"}, - {file = "yarl-1.18.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f275ede6199d0f1ed4ea5d55a7b7573ccd40d97aee7808559e1298fe6efc8dbd"}, - {file = "yarl-1.18.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f7edeb1dcc7f50a2c8e08b9dc13a413903b7817e72273f00878cb70e766bdb3b"}, - {file = "yarl-1.18.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c083f6dd6951b86e484ebfc9c3524b49bcaa9c420cb4b2a78ef9f7a512bfcc85"}, - {file = "yarl-1.18.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:80741ec5b471fbdfb997821b2842c59660a1c930ceb42f8a84ba8ca0f25a66aa"}, - {file = "yarl-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1a3297b9cad594e1ff0c040d2881d7d3a74124a3c73e00c3c71526a1234a9f7"}, - {file = "yarl-1.18.0-cp312-cp312-win32.whl", hash = "sha256:cd6ab7d6776c186f544f893b45ee0c883542b35e8a493db74665d2e594d3ca75"}, - {file = "yarl-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:039c299a0864d1f43c3e31570045635034ea7021db41bf4842693a72aca8df3a"}, - {file = "yarl-1.18.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6fb64dd45453225f57d82c4764818d7a205ee31ce193e9f0086e493916bd4f72"}, - {file = "yarl-1.18.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3adaaf9c6b1b4fc258584f4443f24d775a2086aee82d1387e48a8b4f3d6aecf6"}, - {file = "yarl-1.18.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:da206d1ec78438a563c5429ab808a2b23ad7bc025c8adbf08540dde202be37d5"}, - {file = "yarl-1.18.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:576d258b21c1db4c6449b1c572c75d03f16a482eb380be8003682bdbe7db2f28"}, - {file = "yarl-1.18.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e547c0a375c4bfcdd60eef82e7e0e8698bf84c239d715f5c1278a73050393"}, - {file = "yarl-1.18.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3818eabaefb90adeb5e0f62f047310079d426387991106d4fbf3519eec7d90a"}, - {file = "yarl-1.18.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f72421246c21af6a92fbc8c13b6d4c5427dfd949049b937c3b731f2f9076bd"}, - {file = "yarl-1.18.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fa7d37f2ada0f42e0723632993ed422f2a679af0e200874d9d861720a54f53e"}, - {file = "yarl-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:42ba84e2ac26a3f252715f8ec17e6fdc0cbf95b9617c5367579fafcd7fba50eb"}, - {file = "yarl-1.18.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6a49ad0102c0f0ba839628d0bf45973c86ce7b590cdedf7540d5b1833ddc6f00"}, - {file = "yarl-1.18.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:96404e8d5e1bbe36bdaa84ef89dc36f0e75939e060ca5cd45451aba01db02902"}, - {file = "yarl-1.18.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a0509475d714df8f6d498935b3f307cd122c4ca76f7d426c7e1bb791bcd87eda"}, - {file = "yarl-1.18.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ff116f0285b5c8b3b9a2680aeca29a858b3b9e0402fc79fd850b32c2bcb9f8b"}, - {file = "yarl-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2580c1d7e66e6d29d6e11855e3b1c6381971e0edd9a5066e6c14d79bc8967af"}, - {file = "yarl-1.18.0-cp313-cp313-win32.whl", hash = "sha256:14408cc4d34e202caba7b5ac9cc84700e3421a9e2d1b157d744d101b061a4a88"}, - {file = "yarl-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1db1537e9cb846eb0ff206eac667f627794be8b71368c1ab3207ec7b6f8c5afc"}, - {file = "yarl-1.18.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fa2c9cb607e0f660d48c54a63de7a9b36fef62f6b8bd50ff592ce1137e73ac7d"}, - {file = "yarl-1.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c0f4808644baf0a434a3442df5e0bedf8d05208f0719cedcd499e168b23bfdc4"}, - {file = "yarl-1.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7db9584235895a1dffca17e1c634b13870852094f6389b68dcc6338086aa7b08"}, - {file = "yarl-1.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:309f8d27d6f93ceeeb80aa6980e883aa57895270f7f41842b92247e65d7aeddf"}, - {file = "yarl-1.18.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:609ffd44fed2ed88d9b4ef62ee860cf86446cf066333ad4ce4123505b819e581"}, - {file = "yarl-1.18.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f172b8b2c72a13a06ea49225a9c47079549036ad1b34afa12d5491b881f5b993"}, - {file = "yarl-1.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d89ae7de94631b60d468412c18290d358a9d805182373d804ec839978b120422"}, - {file = "yarl-1.18.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:466d31fd043ef9af822ee3f1df8fdff4e8c199a7f4012c2642006af240eade17"}, - {file = "yarl-1.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7609b8462351c4836b3edce4201acb6dd46187b207c589b30a87ffd1813b48dc"}, - {file = "yarl-1.18.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d9d4f5e471e8dc49b593a80766c2328257e405f943c56a3dc985c125732bc4cf"}, - {file = "yarl-1.18.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:67b336c15e564d76869c9a21316f90edf546809a5796a083b8f57c845056bc01"}, - {file = "yarl-1.18.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b212452b80cae26cb767aa045b051740e464c5129b7bd739c58fbb7deb339e7b"}, - {file = "yarl-1.18.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:38b39b7b3e692b6c92b986b00137a3891eddb66311b229d1940dcbd4f025083c"}, - {file = "yarl-1.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a7ee6884a8848792d58b854946b685521f41d8871afa65e0d4a774954e9c9e89"}, - {file = "yarl-1.18.0-cp39-cp39-win32.whl", hash = "sha256:b4095c5019bb889aa866bf12ed4c85c0daea5aafcb7c20d1519f02a1e738f07f"}, - {file = "yarl-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:2d90f2e4d16a5b0915ee065218b435d2ef619dd228973b1b47d262a6f7cd8fa5"}, - {file = "yarl-1.18.0-py3-none-any.whl", hash = "sha256:dbf53db46f7cf176ee01d8d98c39381440776fcda13779d269a8ba664f69bec0"}, - {file = "yarl-1.18.0.tar.gz", hash = "sha256:20d95535e7d833889982bfe7cc321b7f63bf8879788fee982c76ae2b24cfb715"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.0" - -[metadata] -lock-version = "2.0" -python-versions = ">=3.9,<4.0" -content-hash = "404c89b48c808637d5e403a000972e4263fc84f9721298386186eee9f37e4879" diff --git a/libs/partners/couchbase/pyproject.toml b/libs/partners/couchbase/pyproject.toml deleted file mode 100644 index bc408b1bc67e4..0000000000000 --- a/libs/partners/couchbase/pyproject.toml +++ /dev/null @@ -1,81 +0,0 @@ -[build-system] -requires = [ "poetry-core>=1.0.0",] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "langchain-couchbase" -version = "0.2.2" -description = "An integration package connecting Couchbase and LangChain" -authors = [] -readme = "README.md" -repository = "https://github.com/langchain-ai/langchain" -license = "MIT" - -[tool.mypy] -disallow_untyped_defs = "True" -ignore_missing_imports = "True" - -[tool.poetry.urls] -"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/couchbase" -"Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22langchain-couchbase%3D%3D0%22&expanded=true" - -[tool.poetry.dependencies] -python = ">=3.9,<4.0" -langchain-core = "^0.3.15" -couchbase = "^4.3.2" - -[tool.ruff.lint] -select = [ "E", "F", "I", "T201",] - -[tool.coverage.run] -omit = [ "tests/*",] - -[tool.pytest.ini_options] -addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5" -markers = [ "compile: mark placeholder test used to compile integration tests without running them",] -asyncio_mode = "auto" - -[tool.poetry.group.test] -optional = true - -[tool.poetry.group.codespell] -optional = true - -[tool.poetry.group.test_integration] -optional = true - -[tool.poetry.group.lint] -optional = true - -[tool.poetry.group.dev] -optional = true - -[tool.poetry.group.test.dependencies] -pytest = "^7.4.3" -pytest-asyncio = "^0.23.2" -pytest-socket = "^0.7.0" -syrupy = "^4.0.2" -langchain = "^0.3.0.dev" - -[tool.poetry.group.codespell.dependencies] -codespell = "^2.2.6" - -[tool.poetry.group.test_integration.dependencies] - -[tool.poetry.group.lint.dependencies] -ruff = "^0.5" - -[tool.poetry.group.typing.dependencies] -mypy = "^1.10" - -[tool.poetry.group.test.dependencies.langchain-core] -path = "../../core" -develop = true - -[tool.poetry.group.dev.dependencies.langchain-core] -path = "../../core" -develop = true - -[tool.poetry.group.typing.dependencies.langchain-core] -path = "../../core" -develop = true diff --git a/libs/partners/couchbase/scripts/check_imports.py b/libs/partners/couchbase/scripts/check_imports.py deleted file mode 100644 index 58a460c149353..0000000000000 --- a/libs/partners/couchbase/scripts/check_imports.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys -import traceback -from importlib.machinery import SourceFileLoader - -if __name__ == "__main__": - files = sys.argv[1:] - has_failure = False - for file in files: - try: - SourceFileLoader("x", file).load_module() - except Exception: - has_failure = True - print(file) # noqa: T201 - traceback.print_exc() - print() # noqa: T201 - - sys.exit(1 if has_failure else 0) diff --git a/libs/partners/couchbase/scripts/lint_imports.sh b/libs/partners/couchbase/scripts/lint_imports.sh deleted file mode 100755 index 19ccec1480c01..0000000000000 --- a/libs/partners/couchbase/scripts/lint_imports.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -eu - -# Initialize a variable to keep track of errors -errors=0 - -# make sure not importing from langchain, langchain_experimental, or langchain_community -git --no-pager grep '^from langchain\.' . && errors=$((errors+1)) -git --no-pager grep '^from langchain_experimental\.' . && errors=$((errors+1)) -git --no-pager grep '^from langchain_community\.' . && errors=$((errors+1)) - -# Decide on an exit status based on the errors -if [ "$errors" -gt 0 ]; then - exit 1 -else - exit 0 -fi diff --git a/libs/partners/couchbase/tests/__init__.py b/libs/partners/couchbase/tests/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/libs/partners/couchbase/tests/integration_tests/__init__.py b/libs/partners/couchbase/tests/integration_tests/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/libs/partners/couchbase/tests/integration_tests/test_cache.py b/libs/partners/couchbase/tests/integration_tests/test_cache.py deleted file mode 100644 index 08187c3a462b5..0000000000000 --- a/libs/partners/couchbase/tests/integration_tests/test_cache.py +++ /dev/null @@ -1,218 +0,0 @@ -"""Test Couchbase Cache functionality""" - -import os -from datetime import datetime, timedelta -from typing import Any - -import pytest -from couchbase.auth import PasswordAuthenticator -from couchbase.cluster import Cluster -from couchbase.options import ClusterOptions -from langchain_core.globals import get_llm_cache, set_llm_cache -from langchain_core.outputs import Generation - -from langchain_couchbase.cache import CouchbaseCache, CouchbaseSemanticCache -from tests.utils import ( - FakeEmbeddings, - FakeLLM, - cache_key_hash_function, - fetch_document_expiry_time, - get_document_keys, -) - -CONNECTION_STRING = os.getenv("COUCHBASE_CONNECTION_STRING", "") -BUCKET_NAME = os.getenv("COUCHBASE_BUCKET_NAME", "") -SCOPE_NAME = os.getenv("COUCHBASE_SCOPE_NAME", "") -CACHE_COLLECTION_NAME = os.getenv("COUCHBASE_CACHE_COLLECTION_NAME", "") -SEMANTIC_CACHE_COLLECTION_NAME = os.getenv( - "COUCHBASE_SEMANTIC_CACHE_COLLECTION_NAME", "" -) -USERNAME = os.getenv("COUCHBASE_USERNAME", "") -PASSWORD = os.getenv("COUCHBASE_PASSWORD", "") -INDEX_NAME = os.getenv("COUCHBASE_SEMANTIC_CACHE_INDEX_NAME", "") - - -def set_all_env_vars() -> bool: - """Check if all environment variables are set""" - return all( - [ - CONNECTION_STRING, - BUCKET_NAME, - SCOPE_NAME, - CACHE_COLLECTION_NAME, - USERNAME, - PASSWORD, - INDEX_NAME, - ] - ) - - -def get_cluster() -> Any: - """Get a couchbase cluster object""" - auth = PasswordAuthenticator(USERNAME, PASSWORD) - options = ClusterOptions(auth) - connect_string = CONNECTION_STRING - cluster = Cluster(connect_string, options) - - # Wait until the cluster is ready for use. - cluster.wait_until_ready(timedelta(seconds=5)) - - return cluster - - -@pytest.fixture() -def cluster() -> Any: - """Get a couchbase cluster object""" - return get_cluster() - - -@pytest.mark.skipif( - not set_all_env_vars(), reason="Missing Couchbase environment variables" -) -class TestCouchbaseCache: - def test_cache(self, cluster: Any) -> None: - """Test standard LLM cache functionality""" - set_llm_cache( - CouchbaseCache( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=CACHE_COLLECTION_NAME, - ) - ) - - llm = FakeLLM() - - params = llm.dict() - params["stop"] = None - llm_string = str(sorted([(k, v) for k, v in params.items()])) - get_llm_cache().update("foo", llm_string, [Generation(text="fizz")]) - cache_output = get_llm_cache().lookup("foo", llm_string) - assert cache_output == [Generation(text="fizz")] - - get_llm_cache().clear() - output = get_llm_cache().lookup("bar", llm_string) - assert output != [Generation(text="fizz")] - - def test_cache_with_ttl(self, cluster: Any) -> None: - """Test standard LLM cache functionality with TTL""" - ttl = timedelta(minutes=10) - set_llm_cache( - CouchbaseCache( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=CACHE_COLLECTION_NAME, - ttl=ttl, - ) - ) - - llm = FakeLLM() - - params = llm.dict() - params["stop"] = None - llm_string = str(sorted([(k, v) for k, v in params.items()])) - get_llm_cache().update("foo", llm_string, [Generation(text="fizz")]) - cache_output = get_llm_cache().lookup("foo", llm_string) - assert cache_output == [Generation(text="fizz")] - - # Check the document's expiry time by fetching it from the database - document_key = cache_key_hash_function("foo" + llm_string) - document_expiry_time = fetch_document_expiry_time( - cluster, BUCKET_NAME, SCOPE_NAME, CACHE_COLLECTION_NAME, document_key - ) - current_time = datetime.now() - - time_to_expiry = document_expiry_time - current_time - - assert time_to_expiry < ttl - - def test_semantic_cache(self, cluster: Any) -> None: - """Test semantic LLM cache functionality""" - set_llm_cache( - CouchbaseSemanticCache( - cluster=cluster, - embedding=FakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=SEMANTIC_CACHE_COLLECTION_NAME, - ) - ) - - llm = FakeLLM() - - params = llm.dict() - params["stop"] = None - llm_string = str(sorted([(k, v) for k, v in params.items()])) - get_llm_cache().update( - "foo", llm_string, [Generation(text="fizz"), Generation(text="Buzz")] - ) - - # foo and bar will have the same embedding produced by FakeEmbeddings - cache_output = get_llm_cache().lookup("bar", llm_string) - assert cache_output == [Generation(text="fizz"), Generation(text="Buzz")] - - # clear the cache - get_llm_cache().clear() - output = get_llm_cache().lookup("bar", llm_string) - assert output != [Generation(text="fizz"), Generation(text="Buzz")] - - def test_semantic_cache_with_ttl(self, cluster: Any) -> None: - """Test semantic LLM cache functionality with TTL""" - ttl = timedelta(minutes=10) - - set_llm_cache( - CouchbaseSemanticCache( - cluster=cluster, - embedding=FakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=SEMANTIC_CACHE_COLLECTION_NAME, - ttl=ttl, - ) - ) - - llm = FakeLLM() - - params = llm.dict() - params["stop"] = None - llm_string = str(sorted([(k, v) for k, v in params.items()])) - - # Add a document to the cache - seed_prompt = "foo" - get_llm_cache().update( - seed_prompt, llm_string, [Generation(text="fizz"), Generation(text="Buzz")] - ) - - # foo and bar will have the same embedding produced by FakeEmbeddings - cache_output = get_llm_cache().lookup("bar", llm_string) - assert cache_output == [Generation(text="fizz"), Generation(text="Buzz")] - - # Check the document's expiry time by fetching it from the database - fetch_document_query = ( - f"SELECT meta().id, * from `{SEMANTIC_CACHE_COLLECTION_NAME}` doc " - f"WHERE doc.text = '{seed_prompt}'" - ) - - document_keys = get_document_keys( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - query=fetch_document_query, - ) - assert len(document_keys) == 1 - - document_expiry_time = fetch_document_expiry_time( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=SEMANTIC_CACHE_COLLECTION_NAME, - document_key=document_keys[0], - ) - current_time = datetime.now() - - time_to_expiry = document_expiry_time - current_time - - assert time_to_expiry < ttl diff --git a/libs/partners/couchbase/tests/integration_tests/test_chat_message_history.py b/libs/partners/couchbase/tests/integration_tests/test_chat_message_history.py deleted file mode 100644 index 67076f15cdce9..0000000000000 --- a/libs/partners/couchbase/tests/integration_tests/test_chat_message_history.py +++ /dev/null @@ -1,294 +0,0 @@ -"""Test Couchbase Chat Message History functionality""" - -import os -import time -from datetime import datetime, timedelta -from typing import Any - -import pytest -from couchbase.auth import PasswordAuthenticator -from couchbase.cluster import Cluster -from couchbase.options import ClusterOptions -from langchain.memory import ConversationBufferMemory -from langchain_core.messages import AIMessage, HumanMessage - -from langchain_couchbase.chat_message_histories import CouchbaseChatMessageHistory -from tests.utils import fetch_document_expiry_time, get_document_keys - -CONNECTION_STRING = os.getenv("COUCHBASE_CONNECTION_STRING", "") -BUCKET_NAME = os.getenv("COUCHBASE_BUCKET_NAME", "") -SCOPE_NAME = os.getenv("COUCHBASE_SCOPE_NAME", "") -MESSAGE_HISTORY_COLLECTION_NAME = os.getenv( - "COUCHBASE_CHAT_HISTORY_COLLECTION_NAME", "" -) -USERNAME = os.getenv("COUCHBASE_USERNAME", "") -PASSWORD = os.getenv("COUCHBASE_PASSWORD", "") -SLEEP_DURATION = 0.2 - - -def set_all_env_vars() -> bool: - """Check if all environment variables are set""" - return all( - [ - CONNECTION_STRING, - BUCKET_NAME, - SCOPE_NAME, - MESSAGE_HISTORY_COLLECTION_NAME, - USERNAME, - PASSWORD, - ] - ) - - -def get_cluster() -> Any: - """Get a couchbase cluster object""" - auth = PasswordAuthenticator(USERNAME, PASSWORD) - options = ClusterOptions(auth) - connect_string = CONNECTION_STRING - cluster = Cluster(connect_string, options) - - # Wait until the cluster is ready for use. - cluster.wait_until_ready(timedelta(seconds=5)) - - return cluster - - -@pytest.fixture() -def cluster() -> Any: - """Get a couchbase cluster object""" - return get_cluster() - - -@pytest.mark.skipif( - not set_all_env_vars(), reason="Missing Couchbase environment variables" -) -class TestCouchbaseCache: - def test_memory_with_message_store(self, cluster: Any) -> None: - """Test chat message history with a message store""" - - message_history = CouchbaseChatMessageHistory( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - session_id="test-session", - ) - - memory = ConversationBufferMemory( - memory_key="baz", chat_memory=message_history, return_messages=True - ) - - # clear the memory - memory.chat_memory.clear() - - # wait for the messages to be cleared - time.sleep(SLEEP_DURATION) - assert memory.chat_memory.messages == [] - - # add some messages - ai_message = AIMessage(content="Hello, how are you doing ?") - user_message = HumanMessage(content="I'm good, how are you?") - memory.chat_memory.add_messages([ai_message, user_message]) - - # wait until the messages can be retrieved - time.sleep(SLEEP_DURATION) - - # check that the messages are in the memory - messages = memory.chat_memory.messages - assert len(messages) == 2 - - # check that the messages are in the order of creation - assert messages == [ai_message, user_message] - - # clear the memory - memory.chat_memory.clear() - time.sleep(SLEEP_DURATION) - assert memory.chat_memory.messages == [] - - def test_memory_with_separate_sessions(self, cluster: Any) -> None: - """Test the chat message history with multiple sessions""" - - message_history_a = CouchbaseChatMessageHistory( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - session_id="test-session-a", - ) - - message_history_b = CouchbaseChatMessageHistory( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - session_id="test-session-b", - ) - - memory_a = ConversationBufferMemory( - memory_key="a", chat_memory=message_history_a, return_messages=True - ) - memory_b = ConversationBufferMemory( - memory_key="b", chat_memory=message_history_b, return_messages=True - ) - - # clear the memory - memory_a.chat_memory.clear() - memory_b.chat_memory.clear() - - # add some messages - ai_message = AIMessage(content="Hello, how are you doing ?") - user_message = HumanMessage(content="I'm good, how are you?") - memory_a.chat_memory.add_ai_message(ai_message) - memory_b.chat_memory.add_user_message(user_message) - - # wait until the messages can be retrieved - time.sleep(SLEEP_DURATION) - - # check that the messages are in the memory - messages_a = memory_a.chat_memory.messages - messages_b = memory_b.chat_memory.messages - assert len(messages_a) == 1 - assert len(messages_b) == 1 - assert messages_a == [ai_message] - assert messages_b == [user_message] - - # clear the memory - memory_a.chat_memory.clear() - time.sleep(SLEEP_DURATION) - # ensure that only the session that is cleared is empty - assert memory_a.chat_memory.messages == [] - assert memory_b.chat_memory.messages == [user_message] - - # clear the other session's memory - memory_b.chat_memory.clear() - time.sleep(SLEEP_DURATION) - assert memory_b.chat_memory.messages == [] - - def test_memory_message_with_ttl(self, cluster: Any) -> None: - """Test chat message history with a message being saved with a TTL""" - ttl = timedelta(minutes=5) - session_id = "test-session-ttl" - message_history = CouchbaseChatMessageHistory( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - session_id=session_id, - ttl=ttl, - ) - - memory = ConversationBufferMemory( - memory_key="baz", chat_memory=message_history, return_messages=True - ) - - # clear the memory - memory.chat_memory.clear() - - # wait for the messages to be cleared - time.sleep(SLEEP_DURATION) - assert memory.chat_memory.messages == [] - - # add some messages - ai_message = AIMessage(content="Hello, how are you doing ?") - memory.chat_memory.add_ai_message(ai_message) - - # wait until the messages can be retrieved - time.sleep(SLEEP_DURATION) - - # check that the messages are in the memory - messages = memory.chat_memory.messages - assert len(messages) == 1 - - # check that the messages are in the order of creation - assert messages == [ai_message] - - # Check the document's expiry time by fetching it from the database - fetch_documents_query = ( - f"SELECT meta().id, * from `{MESSAGE_HISTORY_COLLECTION_NAME}` doc" - f" WHERE doc.session_id = '{session_id}'" - ) - - document_keys = get_document_keys( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - query=fetch_documents_query, - ) - assert len(document_keys) == 1 - - # Ensure that the document will expire within the TTL - - document_expiry_time = fetch_document_expiry_time( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - document_key=document_keys[0], - ) - current_time = datetime.now() - assert document_expiry_time - current_time < ttl - - def test_memory_messages_with_ttl(self, cluster: Any) -> None: - """Test chat message history with messages being stored with a TTL""" - ttl = timedelta(minutes=5) - session_id = "test-session-ttl" - message_history = CouchbaseChatMessageHistory( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - session_id=session_id, - ttl=ttl, - ) - - memory = ConversationBufferMemory( - memory_key="baz", chat_memory=message_history, return_messages=True - ) - - # clear the memory - memory.chat_memory.clear() - - # wait for the messages to be cleared - time.sleep(SLEEP_DURATION) - assert memory.chat_memory.messages == [] - - # add some messages - ai_message = AIMessage(content="Hello, how are you doing ?") - user_message = HumanMessage(content="I'm good, how are you?") - memory.chat_memory.add_messages([ai_message, user_message]) - - # wait until the messages can be retrieved - time.sleep(SLEEP_DURATION) - - # check that the messages are in the memory - messages = memory.chat_memory.messages - assert len(messages) == 2 - - # check that the messages are in the order of creation - assert messages == [ai_message, user_message] - - # Check the documents' expiry time by fetching the documents from the database - fetch_documents_query = ( - f"SELECT meta().id, * from `{MESSAGE_HISTORY_COLLECTION_NAME}` doc" - f" WHERE doc.session_id = '{session_id}'" - ) - - document_keys = get_document_keys( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - query=fetch_documents_query, - ) - assert len(document_keys) == 2 - - # Ensure that each document will expire within the TTL - for document_key in document_keys: - document_expiry_time = fetch_document_expiry_time( - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=MESSAGE_HISTORY_COLLECTION_NAME, - document_key=document_key, - ) - current_time = datetime.now() - assert document_expiry_time - current_time < ttl diff --git a/libs/partners/couchbase/tests/integration_tests/test_compile.py b/libs/partners/couchbase/tests/integration_tests/test_compile.py deleted file mode 100644 index 33ecccdfa0fbd..0000000000000 --- a/libs/partners/couchbase/tests/integration_tests/test_compile.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - - -@pytest.mark.compile -def test_placeholder() -> None: - """Used for compiling integration tests without running any real tests.""" - pass diff --git a/libs/partners/couchbase/tests/integration_tests/test_vector_store.py b/libs/partners/couchbase/tests/integration_tests/test_vector_store.py deleted file mode 100644 index ebc6c93f6c873..0000000000000 --- a/libs/partners/couchbase/tests/integration_tests/test_vector_store.py +++ /dev/null @@ -1,396 +0,0 @@ -"""Test Couchbase Vector Store functionality""" - -import os -import time -from typing import Any - -import pytest -from langchain_core.documents import Document - -from langchain_couchbase import CouchbaseVectorStore -from tests.utils import ( - ConsistentFakeEmbeddings, -) - -CONNECTION_STRING = os.getenv("COUCHBASE_CONNECTION_STRING", "") -BUCKET_NAME = os.getenv("COUCHBASE_BUCKET_NAME", "") -SCOPE_NAME = os.getenv("COUCHBASE_SCOPE_NAME", "") -COLLECTION_NAME = os.getenv("COUCHBASE_COLLECTION_NAME", "") -USERNAME = os.getenv("COUCHBASE_USERNAME", "") -PASSWORD = os.getenv("COUCHBASE_PASSWORD", "") -INDEX_NAME = os.getenv("COUCHBASE_INDEX_NAME", "") -SLEEP_DURATION = 1 - - -def set_all_env_vars() -> bool: - return all( - [ - CONNECTION_STRING, - BUCKET_NAME, - SCOPE_NAME, - COLLECTION_NAME, - USERNAME, - PASSWORD, - INDEX_NAME, - ] - ) - - -def get_cluster() -> Any: - """Get a couchbase cluster object""" - from datetime import timedelta - - from couchbase.auth import PasswordAuthenticator - from couchbase.cluster import Cluster - from couchbase.options import ClusterOptions - - auth = PasswordAuthenticator(USERNAME, PASSWORD) - options = ClusterOptions(auth) - connect_string = CONNECTION_STRING - cluster = Cluster(connect_string, options) - - # Wait until the cluster is ready for use. - cluster.wait_until_ready(timedelta(seconds=5)) - - return cluster - - -@pytest.fixture() -def cluster() -> Any: - """Get a couchbase cluster object""" - return get_cluster() - - -def delete_documents( - cluster: Any, bucket_name: str, scope_name: str, collection_name: str -) -> None: - """Delete all the documents in the collection""" - query = f"DELETE FROM `{bucket_name}`.`{scope_name}`.`{collection_name}`" - cluster.query(query).execute() - - -@pytest.mark.skipif( - not set_all_env_vars(), reason="Missing Couchbase environment variables" -) -class TestCouchbaseVectorStore: - @classmethod - def setup_method(self) -> None: - cluster = get_cluster() - # Delete all the documents in the collection - delete_documents(cluster, BUCKET_NAME, SCOPE_NAME, COLLECTION_NAME) - - def test_from_documents(self, cluster: Any) -> None: - """Test end to end search using a list of documents.""" - - documents = [ - Document(page_content="foo", metadata={"page": 1}), - Document(page_content="bar", metadata={"page": 2}), - Document(page_content="baz", metadata={"page": 3}), - ] - - vectorstore = CouchbaseVectorStore.from_documents( - documents, - ConsistentFakeEmbeddings(), - cluster=cluster, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - index_name=INDEX_NAME, - ) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("baz", k=1) - assert output[0].page_content == "baz" - assert output[0].metadata["page"] == 3 - - def test_from_texts(self, cluster: Any) -> None: - """Test end to end search using a list of texts.""" - - texts = [ - "foo", - "bar", - "baz", - ] - - vectorstore = CouchbaseVectorStore.from_texts( - texts, - ConsistentFakeEmbeddings(), - cluster=cluster, - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("foo", k=1) - assert len(output) == 1 - assert output[0].page_content == "foo" - - def test_from_texts_with_metadatas(self, cluster: Any) -> None: - """Test end to end search using a list of texts and metadatas.""" - - texts = [ - "foo", - "bar", - "baz", - ] - - metadatas = [{"a": 1}, {"b": 2}, {"c": 3}] - - vectorstore = CouchbaseVectorStore.from_texts( - texts, - ConsistentFakeEmbeddings(), - metadatas=metadatas, - cluster=cluster, - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("baz", k=1) - assert output[0].page_content == "baz" - assert output[0].metadata["c"] == 3 - - def test_add_texts_with_ids_and_metadatas(self, cluster: Any) -> None: - """Test end to end search by adding a list of texts, ids and metadatas.""" - - texts = [ - "foo", - "bar", - "baz", - ] - - ids = ["a", "b", "c"] - - metadatas = [{"a": 1}, {"b": 2}, {"c": 3}] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - results = vectorstore.add_texts( - texts, - ids=ids, - metadatas=metadatas, - ) - assert results == ids - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("foo", k=1) - assert output[0].id == "a" - assert output[0].page_content == "foo" - assert output[0].metadata["a"] == 1 - - def test_delete_texts_with_ids(self, cluster: Any) -> None: - """Test deletion of documents by ids.""" - texts = [ - "foo", - "bar", - "baz", - ] - - ids = ["a", "b", "c"] - - metadatas = [{"a": 1}, {"b": 2}, {"c": 3}] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - results = vectorstore.add_texts( - texts, - ids=ids, - metadatas=metadatas, - ) - assert results == ids - assert vectorstore.delete(ids) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("foo", k=1) - assert len(output) == 0 - - def test_similarity_search_with_scores(self, cluster: Any) -> None: - """Test similarity search with scores.""" - - texts = ["foo", "bar", "baz"] - - metadatas = [{"a": 1}, {"b": 2}, {"c": 3}] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - vectorstore.add_texts(texts, metadatas=metadatas) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search_with_score("foo", k=2) - - assert len(output) == 2 - assert output[0][0].page_content == "foo" - - # check if the scores are sorted - assert output[0][0].metadata["a"] == 1 - assert output[0][1] > output[1][1] - - def test_similarity_search_by_vector(self, cluster: Any) -> None: - """Test similarity search by vector.""" - - texts = ["foo", "bar", "baz"] - - metadatas = [{"a": 1}, {"b": 2}, {"c": 3}] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - vectorstore.add_texts(texts, metadatas=metadatas) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - vector = ConsistentFakeEmbeddings().embed_query("foo") - vector_output = vectorstore.similarity_search_by_vector(vector, k=1) - - assert vector_output[0].page_content == "foo" - - similarity_output = vectorstore.similarity_search("foo", k=1) - - assert similarity_output == vector_output - - def test_output_fields(self, cluster: Any) -> None: - """Test that output fields are set correctly.""" - - texts = [ - "foo", - "bar", - "baz", - ] - - metadatas = [{"page": 1, "a": 1}, {"page": 2, "b": 2}, {"page": 3, "c": 3}] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - ids = vectorstore.add_texts(texts, metadatas) - assert len(ids) == len(texts) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("foo", k=1, fields=["metadata.page"]) - assert output[0].page_content == "foo" - assert output[0].metadata["page"] == 1 - assert "a" not in output[0].metadata - - def test_hybrid_search(self, cluster: Any) -> None: - """Test hybrid search.""" - - texts = [ - "foo", - "bar", - "baz", - ] - - metadatas = [ - {"section": "index"}, - {"section": "glossary"}, - {"section": "appendix"}, - ] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - vectorstore.add_texts(texts, metadatas=metadatas) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - result, score = vectorstore.similarity_search_with_score("foo", k=1)[0] - - # Wait for the documents to be indexed for hybrid search - time.sleep(SLEEP_DURATION) - - hybrid_result, hybrid_score = vectorstore.similarity_search_with_score( - "foo", - k=1, - search_options={"query": {"match": "index", "field": "metadata.section"}}, - )[0] - - assert result == hybrid_result - assert score <= hybrid_score - - def test_id_in_results(self, cluster: Any) -> None: - """Test that the id is returned in the result documents.""" - - texts = [ - "foo", - "bar", - "baz", - ] - - metadatas = [{"a": 1}, {"b": 2}, {"c": 3}] - - vectorstore = CouchbaseVectorStore( - cluster=cluster, - embedding=ConsistentFakeEmbeddings(), - index_name=INDEX_NAME, - bucket_name=BUCKET_NAME, - scope_name=SCOPE_NAME, - collection_name=COLLECTION_NAME, - ) - - ids = vectorstore.add_texts(texts, metadatas=metadatas) - assert len(ids) == len(texts) - - # Wait for the documents to be indexed - time.sleep(SLEEP_DURATION) - - output = vectorstore.similarity_search("foo", k=1) - assert output[0].id == ids[0] diff --git a/libs/partners/couchbase/tests/unit_tests/__init__.py b/libs/partners/couchbase/tests/unit_tests/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/libs/partners/couchbase/tests/unit_tests/test_imports.py b/libs/partners/couchbase/tests/unit_tests/test_imports.py deleted file mode 100644 index 6fab410176139..0000000000000 --- a/libs/partners/couchbase/tests/unit_tests/test_imports.py +++ /dev/null @@ -1,12 +0,0 @@ -from langchain_couchbase import __all__ - -EXPECTED_ALL = [ - "CouchbaseVectorStore", - "CouchbaseCache", - "CouchbaseSemanticCache", - "CouchbaseChatMessageHistory", -] - - -def test_all_imports() -> None: - assert sorted(EXPECTED_ALL) == sorted(__all__) diff --git a/libs/partners/couchbase/tests/unit_tests/test_vectorstore.py b/libs/partners/couchbase/tests/unit_tests/test_vectorstore.py deleted file mode 100644 index 8b137891791fe..0000000000000 --- a/libs/partners/couchbase/tests/unit_tests/test_vectorstore.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libs/partners/couchbase/tests/utils.py b/libs/partners/couchbase/tests/utils.py deleted file mode 100644 index 1e8a9c3f986e6..0000000000000 --- a/libs/partners/couchbase/tests/utils.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Utilities for testing purposes.""" - -import hashlib -from datetime import datetime -from typing import Any, Dict, List, Mapping, Optional, cast - -from couchbase.cluster import Cluster -from couchbase.options import GetOptions -from langchain_core.callbacks import CallbackManagerForLLMRun -from langchain_core.embeddings import Embeddings -from langchain_core.language_models.llms import LLM - - -class FakeEmbeddings(Embeddings): - """Fake embeddings functionality for testing.""" - - def embed_documents(self, texts: List[str]) -> List[List[float]]: - """Return simple embeddings. - Embeddings encode each text as its index.""" - return [[float(1.0)] * 9 + [float(i)] for i in range(len(texts))] - - async def aembed_documents(self, texts: List[str]) -> List[List[float]]: - return self.embed_documents(texts) - - def embed_query(self, text: str) -> List[float]: - """Return constant query embeddings. - Embeddings are identical to embed_documents(texts)[0]. - Distance to each text will be that text's index, - as it was passed to embed_documents.""" - return [float(1.0)] * 9 + [float(0.0)] - - async def aembed_query(self, text: str) -> List[float]: - return self.embed_query(text) - - -class ConsistentFakeEmbeddings(FakeEmbeddings): - """Fake embeddings which remember all the texts seen so far to return consistent - vectors for the same texts.""" - - def __init__(self, dimensionality: int = 10) -> None: - self.known_texts: List[str] = [] - self.dimensionality = dimensionality - - def embed_documents(self, texts: List[str]) -> List[List[float]]: - """Return consistent embeddings for each text seen so far.""" - out_vectors = [] - for text in texts: - if text not in self.known_texts: - self.known_texts.append(text) - vector = [float(1.0)] * (self.dimensionality - 1) + [ - float(self.known_texts.index(text)) - ] - out_vectors.append(vector) - return out_vectors - - def embed_query(self, text: str) -> List[float]: - """Return consistent embeddings for the text, if seen before, or a constant - one if the text is unknown.""" - return self.embed_documents([text])[0] - - -class FakeLLM(LLM): - """Fake LLM wrapper for testing purposes.""" - - queries: Optional[Mapping] = None - sequential_responses: Optional[bool] = False - response_index: int = 0 - - def get_num_tokens(self, text: str) -> int: - """Return number of tokens.""" - return len(text.split()) - - @property - def _llm_type(self) -> str: - """Return type of llm.""" - return "fake" - - def _call( - self, - prompt: str, - stop: Optional[List[str]] = None, - run_manager: Optional[CallbackManagerForLLMRun] = None, - **kwargs: Any, - ) -> str: - if self.sequential_responses: - return self._get_next_response_in_sequence - if self.queries is not None: - return self.queries[prompt] - if stop is None: - return "foo" - else: - return "bar" - - @property - def _identifying_params(self) -> Dict[str, Any]: - return {} - - @property - def _get_next_response_in_sequence(self) -> str: - queries = cast(Mapping, self.queries) - response = queries[list(queries.keys())[self.response_index]] - self.response_index = self.response_index + 1 - return response - - -def cache_key_hash_function(_input: str) -> str: - """Use a deterministic hashing approach.""" - return hashlib.md5(_input.encode()).hexdigest() - - -def fetch_document_expiry_time( - cluster: Cluster, - bucket_name: str, - scope_name: str, - collection_name: str, - document_key: str, -) -> datetime: - """Fetch the document's expiry time from the database.""" - collection = ( - cluster.bucket(bucket_name).scope(scope_name).collection(collection_name) - ) - result = collection.get(document_key, GetOptions(with_expiry=True)) - - return result.expiryTime - - -def get_document_keys( - cluster: Cluster, bucket_name: str, scope_name: str, query: str -) -> List[str]: - """Get the document key from the database based on the query using meta().id.""" - scope = cluster.bucket(bucket_name).scope(scope_name) - - result = scope.query(query).execute() - - document_keys = [row["id"] for row in result] - return document_keys From 5eb4dc5e068336c84443f723f5c1dcf7a6bd5ce1 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Wed, 15 Jan 2025 15:14:29 -0800 Subject: [PATCH 32/47] standard-tests: double messages test (#29237) --- .../integration_tests/chat_models.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/libs/standard-tests/langchain_tests/integration_tests/chat_models.py b/libs/standard-tests/langchain_tests/integration_tests/chat_models.py index 69116b6cfb7a9..b61733407be3f 100644 --- a/libs/standard-tests/langchain_tests/integration_tests/chat_models.py +++ b/libs/standard-tests/langchain_tests/integration_tests/chat_models.py @@ -557,6 +557,44 @@ def test_conversation(self, model: BaseChatModel) -> None: assert isinstance(result.content, str) assert len(result.content) > 0 + def test_double_messages_conversation(self, model: BaseChatModel) -> None: + """ + Test to verify that the model can handle double-message conversations. + + This should pass for all integrations. Tests the model's ability to process + a sequence of double-system, double-human, and double-ai messages as context + for generating the next response. + + .. dropdown:: Troubleshooting + + First, debug + :meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_invoke` + because this test also uses `model.invoke()`. + + Second, debug + :meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_conversation` + because this test is the "basic case" without double messages. + + If that test passes those but not this one, you should verify that: + 1. Your model API can handle double messages, or the integration should + merge messages before sending them to the API. + 2. The response is a valid :class:`~langchain_core.messages.AIMessage` + """ + messages = [ + SystemMessage("hello"), + SystemMessage("hello"), + HumanMessage("hello"), + HumanMessage("hello"), + AIMessage("hello"), + AIMessage("hello"), + HumanMessage("how are you"), + ] + result = model.invoke(messages) + assert result is not None + assert isinstance(result, AIMessage) + assert isinstance(result.content, str) + assert len(result.content) > 0 + def test_usage_metadata(self, model: BaseChatModel) -> None: """Test to verify that the model returns correct usage metadata. From 4bc6cb759f6d38a136cda837635f7ad679b93b4b Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Wed, 15 Jan 2025 16:03:26 -0800 Subject: [PATCH 33/47] docs: update recommended code interpreters (#29236) unstable :( --- docs/scripts/tool_feat_table.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/scripts/tool_feat_table.py b/docs/scripts/tool_feat_table.py index 4585551f36e12..c92d2344a9318 100644 --- a/docs/scripts/tool_feat_table.py +++ b/docs/scripts/tool_feat_table.py @@ -86,14 +86,6 @@ "link": "/docs/integrations/tools/riza", "self_hosting": True, }, - "E2B Data Analysis": { - "langauges": "Python. In beta: JavaScript, R, Java", - "sandbox_lifetime": "24 Hours", - "upload": True, - "return_results": "Text, Images, Videos", - "link": "/docs/integrations/tools/e2b_data_analysis", - "self_hosting": True, - }, "Azure Container Apps dynamic sessions": { "langauges": "Python", "sandbox_lifetime": "1 Hour", From c6388d736b2844bcd9985f632b66f6e68117bbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BD=90?= <48374300+Q-Bug4@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:05:28 +0800 Subject: [PATCH 34/47] docs: fix typo in tool_results_pass_to_model.ipynb (how-to) (#29252) Description: fix typo. change word from `cals` to `calls` Issue: closes #29251 Dependencies: None Twitter handle: None --- docs/docs/how_to/tool_results_pass_to_model.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/how_to/tool_results_pass_to_model.ipynb b/docs/docs/how_to/tool_results_pass_to_model.ipynb index 6ace9a02c5de1..4263b08749f35 100644 --- a/docs/docs/how_to/tool_results_pass_to_model.ipynb +++ b/docs/docs/how_to/tool_results_pass_to_model.ipynb @@ -16,7 +16,7 @@ "\n", ":::\n", "\n", - "Some models are capable of [**tool calling**](/docs/concepts/tool_calling) - generating arguments that conform to a specific user-provided schema. This guide will demonstrate how to use those tool cals to actually call a function and properly pass the results back to the model.\n", + "Some models are capable of [**tool calling**](/docs/concepts/tool_calling) - generating arguments that conform to a specific user-provided schema. This guide will demonstrate how to use those tool calls to actually call a function and properly pass the results back to the model.\n", "\n", "![Diagram of a tool call invocation](/img/tool_invocation.png)\n", "\n", From 667d2a57fd0b31183b8f53113b54492a9a16a8b9 Mon Sep 17 00:00:00 2001 From: Junon <65576196+Junon-Gz@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:09:04 +0800 Subject: [PATCH 35/47] add mode arg to OBSFileLoader.load() method (#29246) - **Description:** add mode arg to OBSFileLoader.load() method - **Issue:** #29245 - **Dependencies:** no dependencies required for this change --------- Co-authored-by: Junon_Gz --- .../langchain_community/document_loaders/obs_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/obs_file.py b/libs/community/langchain_community/document_loaders/obs_file.py index d6add62c33bc9..f96ad3cd7ecc5 100644 --- a/libs/community/langchain_community/document_loaders/obs_file.py +++ b/libs/community/langchain_community/document_loaders/obs_file.py @@ -92,7 +92,7 @@ def __init__( self.bucket = bucket self.key = key - def load(self) -> List[Document]: + def load(self, mode: str = "single") -> List[Document]: """Load documents.""" with tempfile.TemporaryDirectory() as temp_dir: file_path = f"{temp_dir}/{self.bucket}/{self.key}" @@ -101,5 +101,5 @@ def load(self) -> List[Document]: self.client.downloadFile( bucketName=self.bucket, objectKey=self.key, downloadFile=file_path ) - loader = UnstructuredFileLoader(file_path) + loader = UnstructuredFileLoader(file_path, mode=mode) return loader.load() From 75663f2cae07ce9abc8627bec87fdd9783130bce Mon Sep 17 00:00:00 2001 From: Luis Lopez <144131610+LuisGoCity@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:19:26 +0100 Subject: [PATCH 36/47] community: Add cost per 1K tokens for fine-tuned model cached input (#29248) ### Description - Since there is no cost per 1k input tokens for a fine-tuned cached version of `gpt-4o-mini-2024-07-18` is not available when using the `OpenAICallbackHandler`, it raises an error when trying to make calls with such model. - To add the price in the `MODEL_COST_PER_1K_TOKENS` dictionary cc. @efriis --- libs/community/langchain_community/callbacks/openai_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/community/langchain_community/callbacks/openai_info.py b/libs/community/langchain_community/callbacks/openai_info.py index 5796065f1ee2c..5dee1e22ba3ee 100644 --- a/libs/community/langchain_community/callbacks/openai_info.py +++ b/libs/community/langchain_community/callbacks/openai_info.py @@ -128,6 +128,7 @@ "gpt-3.5-turbo-1106-finetuned": 0.003, "gpt-3.5-turbo-0125-finetuned": 0.003, "gpt-4o-mini-2024-07-18-finetuned": 0.0003, + "gpt-4o-mini-2024-07-18-finetuned-cached": 0.00015, # Fine Tuned output "babbage-002-finetuned-completion": 0.0016, "davinci-002-finetuned-completion": 0.012, From 595297e2e58967b4f083e3ad387f2c0628e42538 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Thu, 16 Jan 2025 14:43:42 -0800 Subject: [PATCH 37/47] core: Add support for calls in get_function_nonlocals (#29255) Thank you for contributing to LangChain! - [ ] **PR title**: "package: description" - Where "package" is whichever of langchain, community, core, etc. is being modified. Use "docs: ..." for purely docs changes, "infra: ..." for CI changes. - Example: "community: add foobar LLM" - [ ] **PR message**: ***Delete this entire checklist*** and replace with - **Description:** a description of the change - **Issue:** the issue # it fixes, if applicable - **Dependencies:** any dependencies required for this change - **Twitter handle:** if your PR gets announced, and you'd like a mention, we'll gladly shout you out! - [ ] **Add tests and docs**: If you're adding a new integration, please include 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/docs/integrations` directory. - [ ] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. See contribution guidelines for more: https://python.langchain.com/docs/contributing/ Additional guidelines: - Make sure optional dependencies are imported within a function. - Please do not add dependencies to pyproject.toml files (even optional ones) unless they are required for unit tests. - Most PRs should not touch more than one package. - Changes should be backwards compatible. - If you are adding something to community, do not re-import it in langchain. If no one reviews your PR within a few days, please @-mention one of baskaryan, efriis, eyurtsev, ccurme, vbarda, hwchase17. --- libs/core/langchain_core/runnables/utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/core/langchain_core/runnables/utils.py b/libs/core/langchain_core/runnables/utils.py index a81ca7615a66f..89e3651dcce4c 100644 --- a/libs/core/langchain_core/runnables/utils.py +++ b/libs/core/langchain_core/runnables/utils.py @@ -271,6 +271,20 @@ def visit_Attribute(self, node: ast.Attribute) -> Any: if isinstance(parent, ast.Name): self.loads.add(parent.id + "." + attr_expr) self.loads.discard(parent.id) + elif isinstance(parent, ast.Call): + if isinstance(parent.func, ast.Name): + self.loads.add(parent.func.id) + else: + parent = parent.func + attr_expr = "" + while isinstance(parent, ast.Attribute): + if attr_expr: + attr_expr = parent.attr + "." + attr_expr + else: + attr_expr = parent.attr + parent = parent.value + if isinstance(parent, ast.Name): + self.loads.add(parent.id + "." + attr_expr) class FunctionNonLocals(ast.NodeVisitor): From d5360b9bd6bdcee4160e38ee668c53630ab3a509 Mon Sep 17 00:00:00 2001 From: ccurme Date: Thu, 16 Jan 2025 17:52:37 -0500 Subject: [PATCH 38/47] core[patch]: release 0.3.30 (#29256) --- libs/core/poetry.lock | 6 +++--- libs/core/pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/core/poetry.lock b/libs/core/poetry.lock index eadfb8d460c70..17cafbf8ed334 100644 --- a/libs/core/poetry.lock +++ b/libs/core/poetry.lock @@ -1200,7 +1200,7 @@ files = [ [[package]] name = "langchain-tests" -version = "0.3.7" +version = "0.3.8" description = "Standard tests for LangChain implementations" optional = false python-versions = ">=3.9,<4.0" @@ -1225,7 +1225,7 @@ url = "../standard-tests" [[package]] name = "langchain-text-splitters" -version = "0.3.4" +version = "0.3.5" description = "LangChain text splitting utilities" optional = false python-versions = ">=3.9,<4.0" @@ -1233,7 +1233,7 @@ files = [] develop = true [package.dependencies] -langchain-core = "^0.3.26" +langchain-core = "^0.3.29" [package.source] type = "directory" diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 65e40fafb09f8..489bda0580214 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "langchain-core" -version = "0.3.29" +version = "0.3.30" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From 36ff83a0b57ca6ea11d2ec80bf974b6c7323bb35 Mon Sep 17 00:00:00 2001 From: Michael Chin Date: Fri, 17 Jan 2025 06:06:17 -0800 Subject: [PATCH 39/47] docs: Message history for Neptune chains (#29260) Expanded the Amazon Neptune documentation with new sections detailing usage of chat message history with the `create_neptune_opencypher_qa_chain` and `create_neptune_sparql_qa_chain` functions. --- .../graphs/amazon_neptune_open_cypher.ipynb | 176 +++++++++++++++- .../graphs/amazon_neptune_sparql.ipynb | 197 ++++++++++++++++-- 2 files changed, 342 insertions(+), 31 deletions(-) diff --git a/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb b/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb index e25719a1b63c2..ba3084e160dc0 100644 --- a/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb +++ b/docs/docs/integrations/graphs/amazon_neptune_open_cypher.ipynb @@ -70,9 +70,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Austin airport has 98 outgoing routes.\n" + ] + } + ], "source": [ "from langchain_aws import ChatBedrockConverse\n", "from langchain_aws.chains import create_neptune_opencypher_qa_chain\n", @@ -83,13 +91,161 @@ " temperature=0,\n", ")\n", "\n", - "chain = create_neptune_opencypher_qa_chain(\n", - " llm=llm,\n", - " graph=graph,\n", - ")\n", + "chain = create_neptune_opencypher_qa_chain(llm=llm, graph=graph)\n", + "\n", + "result = chain.invoke(\"How many outgoing routes does the Austin airport have?\")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adding Message History\n", + "\n", + "The Neptune openCypher QA chain has the ability to be wrapped by [`RunnableWithMessageHistory`](https://python.langchain.com/v0.2/api_reference/core/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html#langchain_core.runnables.history.RunnableWithMessageHistory). This adds message history to the chain, allowing us to create a chatbot that retains conversation state across multiple invocations.\n", + "\n", + "To start, we need a way to store and load the message history. For this purpose, each thread will be created as an instance of [`InMemoryChatMessageHistory`](https://python.langchain.com/api_reference/core/chat_history/langchain_core.chat_history.InMemoryChatMessageHistory.html), and stored into a dictionary for repeated access.\n", + "\n", + "(Also see: https://python.langchain.com/docs/versions/migrating_memory/chat_history/#chatmessagehistory)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.chat_history import InMemoryChatMessageHistory\n", + "\n", + "chats_by_session_id = {}\n", + "\n", + "\n", + "def get_chat_history(session_id: str) -> InMemoryChatMessageHistory:\n", + " chat_history = chats_by_session_id.get(session_id)\n", + " if chat_history is None:\n", + " chat_history = InMemoryChatMessageHistory()\n", + " chats_by_session_id[session_id] = chat_history\n", + " return chat_history" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the QA chain and message history storage can be used to create the new `RunnableWithMessageHistory`. Note that we must set `query` as the input key to match the format expected by the base chain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "\n", + "runnable_with_history = RunnableWithMessageHistory(\n", + " chain,\n", + " get_chat_history,\n", + " input_messages_key=\"query\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before invoking the chain, a unique `session_id` needs to be generated for the conversation that the new `InMemoryChatMessageHistory` will remember." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", "\n", - "result = chain.invoke(\n", - " {\"query\": \"How many outgoing routes does the Austin airport have?\"}\n", + "session_id = uuid.uuid4()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, invoke the message history enabled chain with the `session_id`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You can fly directly to 98 destinations from Austin airport.\n" + ] + } + ], + "source": [ + "result = runnable_with_history.invoke(\n", + " {\"query\": \"How many destinations can I fly to directly from Austin airport?\"},\n", + " config={\"configurable\": {\"session_id\": session_id}},\n", + ")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the chain continues to be invoked with the same `session_id`, responses will be returned in the context of previous queries in the conversation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You can fly directly to 4 destinations in Europe from Austin airport.\n" + ] + } + ], + "source": [ + "result = runnable_with_history.invoke(\n", + " {\"query\": \"Out of those destinations, how many are in Europe?\"},\n", + " config={\"configurable\": {\"session_id\": session_id}},\n", + ")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The four European destinations you can fly to directly from Austin airport are:\n", + "- AMS (Amsterdam Airport Schiphol)\n", + "- FRA (Frankfurt am Main)\n", + "- LGW (London Gatwick)\n", + "- LHR (London Heathrow)\n" + ] + } + ], + "source": [ + "result = runnable_with_history.invoke(\n", + " {\"query\": \"Give me the codes and names of those airports.\"},\n", + " config={\"configurable\": {\"session_id\": session_id}},\n", ")\n", "print(result[\"result\"].content)" ] @@ -97,7 +253,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -111,7 +267,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb b/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb index 3fb1811239895..17dd065067455 100644 --- a/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb +++ b/docs/docs/integrations/graphs/amazon_neptune_sparql.ipynb @@ -48,7 +48,7 @@ "\n", "Seed the W3C organizational data, W3C org ontology plus some instances. \n", " \n", - "You will need an S3 bucket in the same region and account. Set `STAGE_BUCKET`as the name of that bucket." + "You will need an S3 bucket in the same region and account as the Neptune cluster. Set `STAGE_BUCKET`as the name of that bucket." ] }, { @@ -60,11 +60,6 @@ "STAGE_BUCKET = \"\"" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "" - }, { "cell_type": "code", "execution_count": null, @@ -89,7 +84,50 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Bulk-load the org ttl - both ontology and instances" + "We will use the `%load` magic command from the `graph-notebook` package to insert the W3C data into the Neptune graph. Before running `%load`, use `%%graph_notebook_config` to set the graph connection parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install --upgrade --quiet graph-notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext graph_notebook.magics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%graph_notebook_config\n", + "{\n", + " \"host\": \"\",\n", + " \"neptune_service\": \"neptune-db\",\n", + " \"port\": 8182,\n", + " \"auth_mode\": \"<[DEFAULT|IAM]>\",\n", + " \"load_from_s3_arn\": \"\",\n", + " \"ssl\": true,\n", + " \"aws_region\": \"\"\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Bulk-load the org ttl - both ontology and instances." ] }, { @@ -246,7 +284,9 @@ { "cell_type": "markdown", "metadata": {}, - "source": "### Create the Neptune Database RDF Graph" + "source": [ + "### Create the Neptune Database RDF Graph" + ] }, { "cell_type": "code", @@ -297,7 +337,7 @@ " examples=EXAMPLES,\n", ")\n", "\n", - "result = chain.invoke({\"query\": \"How many organizations are in the graph?\"})\n", + "result = chain.invoke(\"How many organizations are in the graph?\")\n", "print(result[\"result\"].content)" ] }, @@ -305,7 +345,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Extra questions\n", "Here are a few more prompts to try on the graph data that was ingested.\n" ] }, @@ -315,7 +354,8 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke({\"query\": \"Are there any mergers or acquisitions?\"})" + "result = chain.invoke(\"Are there any mergers or acquisitions?\")\n", + "print(result[\"result\"].content)" ] }, { @@ -324,7 +364,8 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke({\"query\": \"Find organizations.\"})" + "result = chain.invoke(\"Find organizations.\")\n", + "print(result[\"result\"].content)" ] }, { @@ -333,7 +374,8 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke({\"query\": \"Find sites of MegaSystems or MegaFinancial.\"})" + "result = chain.invoke(\"Find sites of MegaSystems or MegaFinancial.\")\n", + "print(result[\"result\"].content)" ] }, { @@ -342,7 +384,43 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke({\"query\": \"Find a member who is a manager of one or more members.\"})" + "result = chain.invoke(\"Find a member who is a manager of one or more members.\")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke(\"Find five members and their managers.\")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke(\n", + " \"Find org units or suborganizations of The Mega Group. What are the sites of those units?\"\n", + ")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adding Message History\n", + "\n", + "The Neptune SPARQL QA chain has the ability to be wrapped by [`RunnableWithMessageHistory`](https://python.langchain.com/v0.2/api_reference/core/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html#langchain_core.runnables.history.RunnableWithMessageHistory). This adds message history to the chain, allowing us to create a chatbot that retains conversation state across multiple invocations.\n", + "\n", + "To start, we need a way to store and load the message history. For this purpose, each thread will be created as an instance of [`InMemoryChatMessageHistory`](https://python.langchain.com/api_reference/core/chat_history/langchain_core.chat_history.InMemoryChatMessageHistory.html), and stored into a dictionary for repeated access.\n", + "\n", + "(Also see: https://python.langchain.com/docs/versions/migrating_memory/chat_history/#chatmessagehistory)" ] }, { @@ -351,7 +429,24 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke({\"query\": \"Find five members and their managers.\"})" + "from langchain_core.chat_history import InMemoryChatMessageHistory\n", + "\n", + "chats_by_session_id = {}\n", + "\n", + "\n", + "def get_chat_history(session_id: str) -> InMemoryChatMessageHistory:\n", + " chat_history = chats_by_session_id.get(session_id)\n", + " if chat_history is None:\n", + " chat_history = InMemoryChatMessageHistory()\n", + " chats_by_session_id[session_id] = chat_history\n", + " return chat_history" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the QA chain and message history storage can be used to create the new `RunnableWithMessageHistory`. Note that we must set `query` as the input key to match the format expected by the base chain." ] }, { @@ -360,17 +455,77 @@ "metadata": {}, "outputs": [], "source": [ - "chain.invoke(\n", - " {\n", - " \"query\": \"Find org units or suborganizations of The Mega Group. What are the sites of those units?\"\n", - " }\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "\n", + "runnable_with_history = RunnableWithMessageHistory(\n", + " chain,\n", + " get_chat_history,\n", + " input_messages_key=\"query\",\n", ")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before invoking the chain, a unique `session_id` needs to be generated for the conversation that the new `InMemoryChatMessageHistory` will remember.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "session_id = uuid.uuid4()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, invoke the message history enabled chain with the `session_id`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = runnable_with_history.invoke(\n", + " {\"query\": \"How many org units or suborganizations does the The Mega Group have?\"},\n", + " config={\"configurable\": {\"session_id\": session_id}},\n", + ")\n", + "print(result[\"result\"].content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the chain continues to be invoked with the same `session_id`, responses will be returned in the context of previous queries in the conversation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = runnable_with_history.invoke(\n", + " {\"query\": \"List the sites for each of the units.\"},\n", + " config={\"configurable\": {\"session_id\": session_id}},\n", + ")\n", + "print(result[\"result\"].content)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -384,7 +539,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.13" } }, "nbformat": 4, From f0226135e5632b4d29364159bc386009b16dbc91 Mon Sep 17 00:00:00 2001 From: Jun He Date: Fri, 17 Jan 2025 06:30:58 -0800 Subject: [PATCH 40/47] docs: Remove redundant "%" (#29205) Before this commit, the copied command can't be used directly. --- docs/docs/tutorials/classification.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorials/classification.ipynb b/docs/docs/tutorials/classification.ipynb index c869efc1524e5..b61ab56e3c38e 100644 --- a/docs/docs/tutorials/classification.ipynb +++ b/docs/docs/tutorials/classification.ipynb @@ -49,7 +49,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install --upgrade --quiet langchain-core" + "pip install --upgrade --quiet langchain-core" ] }, { From 97a5bc7fc72324d2ad9fee42ff7e0e522d0acd49 Mon Sep 17 00:00:00 2001 From: Zapiron <125368863+DangerousPotential@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:17:40 +0800 Subject: [PATCH 41/47] docs: Fixed typos and improve metadata explanation (#29266) Fix mini typos and made the explanation of metadata filtering clearer --- docs/docs/concepts/vectorstores.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/concepts/vectorstores.mdx b/docs/docs/concepts/vectorstores.mdx index 1a909453744a9..3493d6b0d2bf1 100644 --- a/docs/docs/concepts/vectorstores.mdx +++ b/docs/docs/concepts/vectorstores.mdx @@ -151,10 +151,10 @@ Many vectorstores support [the `k`](/docs/integrations/vectorstores/pinecone/#qu ### Metadata filtering While vectorstore implement a search algorithm to efficiently search over *all* the embedded documents to find the most similar ones, many also support filtering on metadata. -This allows structured filters to reduce the size of the similarity search space. These two concepts work well together: +Metadata filtering helps narrow down the search by applying specific conditions such as retrieving documents from a particular source or date range. These two concepts work well together: -1. **Semantic search**: Query the unstructured data directly, often using via embedding or keyword similarity. -2. **Metadata search**: Apply structured query to the metadata, filering specific documents. +1. **Semantic search**: Query the unstructured data directly, often via embedding or keyword similarity. +2. **Metadata search**: Apply structured query to the metadata, filtering specific documents. Vector store support for metadata filtering is typically dependent on the underlying vector store implementation. From 628145b172216f944d9344c9c91b80c2d7475c81 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Fri, 17 Jan 2025 10:41:59 -0800 Subject: [PATCH 42/47] infra: fix api build (#29274) --- .github/scripts/prep_api_docs_build.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/scripts/prep_api_docs_build.py b/.github/scripts/prep_api_docs_build.py index acc0c257680fb..0d17449a9a2ad 100644 --- a/.github/scripts/prep_api_docs_build.py +++ b/.github/scripts/prep_api_docs_build.py @@ -82,6 +82,12 @@ def main(): and p["repo"] != "langchain-ai/langchain" ]) + # Delete ones without a pyproject.toml + for partner in Path("langchain/libs/partners").iterdir(): + if partner.is_dir() and not (partner / "pyproject.toml").exists(): + print(f"Removing {partner} as it does not have a pyproject.toml") + shutil.rmtree(partner) + print("Library sync completed successfully!") except Exception as e: From c616b445f270f097c4c13e32691d11c54c6d7cb2 Mon Sep 17 00:00:00 2001 From: ccurme Date: Fri, 17 Jan 2025 14:41:41 -0500 Subject: [PATCH 43/47] anthropic[patch]: support `parallel_tool_calls` (#29257) Need to: - Update docs - Decide if this is an explicit kwarg of bind_tools - Decide if this should be in standard test with flag for supporting --- .../langchain_anthropic/chat_models.py | 18 ++++++++++++++++++ .../integration_tests/test_chat_models.py | 19 +++++++++++++++++++ .../chat_models/test_base.py | 12 ++++++++++++ 3 files changed, 49 insertions(+) diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index fd64b824a8df7..842d30ae00da3 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -819,6 +819,7 @@ def bind_tools( tool_choice: Optional[ Union[Dict[str, str], Literal["any", "auto"], str] ] = None, + parallel_tool_calls: Optional[bool] = None, **kwargs: Any, ) -> Runnable[LanguageModelInput, BaseMessage]: r"""Bind tool-like objects to this chat model. @@ -832,6 +833,10 @@ def bind_tools( - name of the tool as a string or as dict ``{"type": "tool", "name": "<>"}``: calls corresponding tool; - ``"auto"``, ``{"type: "auto"}``, or None: automatically selects a tool (including no tool); - ``"any"`` or ``{"type: "any"}``: force at least one tool to be called; + parallel_tool_calls: Set to ``False`` to disable parallel tool use. + Defaults to ``None`` (no specification, which allows parallel tool use). + + .. versionadded:: 0.3.2 kwargs: Any additional parameters are passed directly to :meth:`~langchain_anthropic.chat_models.ChatAnthropic.bind`. @@ -968,6 +973,19 @@ class GetPrice(BaseModel): f"Unrecognized 'tool_choice' type {tool_choice=}. Expected dict, " f"str, or None." ) + + if parallel_tool_calls is not None: + disable_parallel_tool_use = not parallel_tool_calls + if "tool_choice" in kwargs: + kwargs["tool_choice"]["disable_parallel_tool_use"] = ( + disable_parallel_tool_use + ) + else: + kwargs["tool_choice"] = { + "type": "any", + "disable_parallel_tool_use": disable_parallel_tool_use, + } + return self.bind(tools=formatted_tools, **kwargs) def with_structured_output( diff --git a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py index 9f2eaba45549d..3da7bb94f9cf8 100644 --- a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py +++ b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py @@ -444,6 +444,25 @@ def test_tool_use() -> None: assert len(chunks) > 1 +class GenerateUsername(BaseModel): + "Get a username based on someone's name and hair color." + + name: str + hair_color: str + + +def test_disable_parallel_tool_calling() -> None: + llm = ChatAnthropic(model="claude-3-5-sonnet-20241022") + llm_with_tools = llm.bind_tools([GenerateUsername], parallel_tool_calls=False) + result = llm_with_tools.invoke( + "Use the GenerateUsername tool to generate user names for:\n\n" + "Sally with green hair\n" + "Bob with blue hair" + ) + assert isinstance(result, AIMessage) + assert len(result.tool_calls) == 1 + + def test_anthropic_with_empty_text_block() -> None: """Anthropic SDK can return an empty text block.""" diff --git a/libs/partners/openai/tests/integration_tests/chat_models/test_base.py b/libs/partners/openai/tests/integration_tests/chat_models/test_base.py index 49d894603d726..525020192ba0d 100644 --- a/libs/partners/openai/tests/integration_tests/chat_models/test_base.py +++ b/libs/partners/openai/tests/integration_tests/chat_models/test_base.py @@ -630,6 +630,18 @@ def test_bind_tools_tool_choice() -> None: assert not msg.tool_calls +def test_disable_parallel_tool_calling() -> None: + llm = ChatOpenAI(model="gpt-4o-mini") + llm_with_tools = llm.bind_tools([GenerateUsername], parallel_tool_calls=False) + result = llm_with_tools.invoke( + "Use the GenerateUsername tool to generate user names for:\n\n" + "Sally with green hair\n" + "Bob with blue hair" + ) + assert isinstance(result, AIMessage) + assert len(result.tool_calls) == 1 + + @pytest.mark.parametrize("model", ["gpt-4o-mini", "o1"]) def test_openai_structured_output(model: str) -> None: class MyModel(BaseModel): From ac520210972caecadacf58fa6df44d873da2aeb7 Mon Sep 17 00:00:00 2001 From: ccurme Date: Fri, 17 Jan 2025 14:48:31 -0500 Subject: [PATCH 44/47] anthropic[patch]: release 0.3.2 (#29275) --- libs/partners/anthropic/poetry.lock | 8 ++++---- libs/partners/anthropic/pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/partners/anthropic/poetry.lock b/libs/partners/anthropic/poetry.lock index 8dc1383dda9d3..5ebe86a8757a2 100644 --- a/libs/partners/anthropic/poetry.lock +++ b/libs/partners/anthropic/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -453,7 +453,7 @@ files = [ [[package]] name = "langchain-core" -version = "0.3.27" +version = "0.3.30" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9,<4.0" @@ -478,7 +478,7 @@ url = "../../core" [[package]] name = "langchain-tests" -version = "0.3.7" +version = "0.3.8" description = "Standard tests for LangChain implementations" optional = false python-versions = ">=3.9,<4.0" @@ -1357,4 +1357,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "743255e1fe91bc0e2185a1bfe1b4c6bb13f6522affe5bb35bdfda419e93736ae" +content-hash = "882c131a2202eb925560eaf821003a5c0b007131ceb3954bbbf6b1722f756e97" diff --git a/libs/partners/anthropic/pyproject.toml b/libs/partners/anthropic/pyproject.toml index 645f6b5d604fa..a57fe6c4a6f35 100644 --- a/libs/partners/anthropic/pyproject.toml +++ b/libs/partners/anthropic/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "langchain-anthropic" -version = "0.3.1" +version = "0.3.2" description = "An integration package connecting AnthropicMessages and LangChain" authors = [] readme = "README.md" @@ -22,7 +22,7 @@ plugins = ['pydantic.mypy'] [tool.poetry.dependencies] python = ">=3.9,<4.0" anthropic = ">=0.41.0,<1" -langchain-core = "^0.3.27" +langchain-core = "^0.3.30" pydantic = "^2.7.4" [tool.ruff.lint] From 184ea8aeb2be68e184c5937f8d51df22cc8ff081 Mon Sep 17 00:00:00 2001 From: ccurme Date: Fri, 17 Jan 2025 15:26:33 -0500 Subject: [PATCH 45/47] anthropic[patch]: update tool choice type (#29276) --- libs/partners/anthropic/langchain_anthropic/chat_models.py | 2 +- libs/partners/anthropic/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index 842d30ae00da3..af7045d74aeb2 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -982,7 +982,7 @@ class GetPrice(BaseModel): ) else: kwargs["tool_choice"] = { - "type": "any", + "type": "auto", "disable_parallel_tool_use": disable_parallel_tool_use, } diff --git a/libs/partners/anthropic/pyproject.toml b/libs/partners/anthropic/pyproject.toml index a57fe6c4a6f35..16ceb2ba6b636 100644 --- a/libs/partners/anthropic/pyproject.toml +++ b/libs/partners/anthropic/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "langchain-anthropic" -version = "0.3.2" +version = "0.3.3" description = "An integration package connecting AnthropicMessages and LangChain" authors = [] readme = "README.md" From d4b9404fd69f53c60728b0d5c0f6535782aa6891 Mon Sep 17 00:00:00 2001 From: Amaan Date: Sat, 18 Jan 2025 05:32:28 +0530 Subject: [PATCH 46/47] docs: add langchain dappier tool integration notebook (#29265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tools to interact with Dappier APIs with an example notebook. For `DappierRealTimeSearchTool`, the tool can be invoked with: ```python from langchain_dappier import DappierRealTimeSearchTool tool = DappierRealTimeSearchTool() tool.invoke({"query": "What happened at the last wimbledon"}) ``` ``` At the last Wimbledon in 2024, Carlos Alcaraz won the title by defeating Novak Djokovic. This victory marked Alcaraz's fourth Grand Slam title at just 21 years old! 🎉🏆🎾 ``` For DappierAIRecommendationTool the tool can be invoked with: ```python from langchain_dappier import DappierAIRecommendationTool tool = DappierAIRecommendationTool( data_model_id="dm_01j0pb465keqmatq9k83dthx34", similarity_top_k=3, ref="sportsnaut.com", num_articles_ref=2, search_algorithm="most_recent", ) ``` ``` [{"author": "Matt Weaver", "image_url": "https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34...", "pubdate": "Fri, 17 Jan 2025 08:04:03 +0000", "source_url": "https://sportsnaut.com/chili-bowl-thursday-bell-column/", "summary": "The article highlights the thrilling unpredictability... ", "title": "Thursday proves why every lap of Chili Bowl..."}, {"author": "Matt Higgins", "image_url": "https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34...", "pubdate": "Fri, 17 Jan 2025 02:48:42 +0000", "source_url": "https://sportsnaut.com/new-york-mets-news-pete-alonso...", "summary": "The New York Mets are likely parting ways with star...", "title": "MLB insiders reveal New York Mets’ last-ditch..."}, {"author": "Jim Cerny", "image_url": "https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34...", "pubdate": "Fri, 17 Jan 2025 05:10:39 +0000", "source_url": "https://www.foreverblueshirts.com/new-york-rangers-news...", "summary": "The New York Rangers achieved a thrilling 5-3 comeback... ", "title": "Rangers score 3 times in 3rd period for stirring 5-3..."}] ``` The integration package can be found over here - https://github.com/DappierAI/langchain-dappier --- docs/docs/integrations/providers/dappier.mdx | 11 + .../integrations/retrievers/dappier.ipynb | 8 - docs/docs/integrations/tools/dappier.ipynb | 504 ++++++++++++++++++ 3 files changed, 515 insertions(+), 8 deletions(-) create mode 100644 docs/docs/integrations/tools/dappier.ipynb diff --git a/docs/docs/integrations/providers/dappier.mdx b/docs/docs/integrations/providers/dappier.mdx index 7e15d7b7289e9..2a1235984f106 100644 --- a/docs/docs/integrations/providers/dappier.mdx +++ b/docs/docs/integrations/providers/dappier.mdx @@ -46,3 +46,14 @@ See a [usage example](/docs/integrations/retrievers/dappier). ```python from langchain_dappier import DappierRetriever ``` + +## Tool + +See a [usage example](/docs/integrations/tools/dappier). + +```python +from langchain_dappier import ( + DappierRealTimeSearchTool, + DappierAIRecommendationTool +) +``` diff --git a/docs/docs/integrations/retrievers/dappier.ipynb b/docs/docs/integrations/retrievers/dappier.ipynb index 5ceeb25d0ab12..94cf86c8ca4ca 100644 --- a/docs/docs/integrations/retrievers/dappier.ipynb +++ b/docs/docs/integrations/retrievers/dappier.ipynb @@ -25,14 +25,6 @@ "\n", "This will help you getting started with the Dappier [retriever](https://python.langchain.com/docs/concepts/retrievers/). For detailed documentation of all DappierRetriever features and configurations head to the [API reference](https://python.langchain.com/en/latest/retrievers/langchain_dappier.retrievers.Dappier.DappierRetriever.html).\n", "\n", - "### Integration details\n", - "\n", - "Bring-your-own data (i.e., index and search a custom corpus of documents):\n", - "\n", - "| Retriever | Self-host | Cloud offering | Package |\n", - "| :--- | :--- | :---: | :---: |\n", - "[DappierRetriever](https://python.langchain.com/en/latest/retrievers/langchain_dappier.retrievers.Dappier.DappierRetriever.html) | ❌ | ❌ | langchain-dappier |\n", - "\n", "### Setup\n", "\n", "Install ``langchain-dappier`` and set environment variable ``DAPPIER_API_KEY``.\n", diff --git a/docs/docs/integrations/tools/dappier.ipynb b/docs/docs/integrations/tools/dappier.ipynb new file mode 100644 index 0000000000000..52ef3b5656783 --- /dev/null +++ b/docs/docs/integrations/tools/dappier.ipynb @@ -0,0 +1,504 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "BJB3NSYqUWl4" + }, + "source": [ + "# Dappier\n", + "\n", + "[Dappier](https://dappier.com) connects any LLM or your Agentic AI to real-time, rights-cleared, proprietary data from trusted sources, making your AI an expert in anything. Our specialized models include Real-Time Web Search, News, Sports, Financial Stock Market Data, Crypto Data, and exclusive content from premium publishers. Explore a wide range of data models in our marketplace at [marketplace.dappier.com](https://marketplace.dappier.com).\n", + "\n", + "[Dappier](https://dappier.com) delivers enriched, prompt-ready, and contextually relevant data strings, optimized for seamless integration with LangChain. Whether you're building conversational AI, recommendation engines, or intelligent search, Dappier's LLM-agnostic RAG models ensure your AI has access to verified, up-to-date data—without the complexity of building and managing your own retrieval pipeline." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MAbkvI8pUb7R" + }, + "source": [ + "# Dappier Tool\n", + "\n", + "This will help you getting started with the Dappier [tool](https://python.langchain.com/docs/concepts/tools/). For detailed documentation of all DappierRetriever features and configurations head to the [API reference](https://python.langchain.com/en/latest/tools/langchain_dappier.tools.Dappier.DappierRealTimeSearchTool.html).\n", + "\n", + "## Overview\n", + "\n", + "The DappierRealTimeSearchTool and DappierAIRecommendationTool empower AI applications with real-time data and AI-driven insights. The former provides access to up-to-date information across news, weather, travel, and financial markets, while the latter supercharges applications with factual, premium content from diverse domains like News, Finance, and Sports, all powered by Dappier's pre-trained RAG models and natural language APIs." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ouA6p-E-aF34" + }, + "source": [ + "### Setup\n", + "\n", + "This tool lives in the `langchain-dappier` package." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "tNhKB0BUaneq" + }, + "outputs": [], + "source": [ + "%pip install -qU langchain-dappier" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oBLVZpnoYshG" + }, + "source": [ + "### Credentials\n", + "\n", + "We also need to set our Dappier API credentials, which can be generated at the [Dappier site.](https://platform.dappier.com/profile/api-keys)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UrmBR_JyY9I6" + }, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "if not os.environ.get(\"DAPPIER_API_KEY\"):\n", + " os.environ[\"DAPPIER_API_KEY\"] = getpass.getpass(\"Dappier API key:\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "x_3712yIXTGc" + }, + "source": [ + "If you want to get automated tracing from individual queries, you can also set your [LangSmith](https://docs.smith.langchain.com/) API key by uncommenting below:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "id": "S1Tuwpq-XVaX" + }, + "outputs": [], + "source": [ + "# os.environ[\"LANGSMITH_API_KEY\"] = getpass.getpass(\"Enter your LangSmith API key: \")\n", + "# os.environ[\"LANGSMITH_TRACING\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IgPgQ12wcA4i" + }, + "source": [ + "## DappierRealTimeSearchTool\n", + "\n", + "Access real-time Google search results, including the latest news, weather, travel, and deals, along with up-to-date financial news, stock prices, and trades from polygon.io, all powered by AI insights to keep you informed." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "W8QBSmDvbL69" + }, + "source": [ + "### Instantiation\n", + "\n", + "- ai_model_id: str\n", + " The AI model ID to use for the query. The AI model ID always starts\n", + " with the prefix \"am_\".\n", + "\n", + " Defaults to \"am_01j06ytn18ejftedz6dyhz2b15\".\n", + "\n", + " Multiple AI model IDs are available, which can be found at:\n", + " https://marketplace.dappier.com/marketplace" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "tw1edqrLbiJ4" + }, + "outputs": [], + "source": [ + "from langchain_dappier import DappierRealTimeSearchTool\n", + "\n", + "tool = DappierRealTimeSearchTool(\n", + " # ai_model_id=\"...\", # overwrite default ai_model_id\n", + " # name=\"...\", # overwrite default tool name\n", + " # description=\"...\", # overwrite default tool description\n", + " # args_schema=..., # overwrite default args_schema: BaseModel\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nTatJ6F8b0sV" + }, + "source": [ + "### Invocation\n", + "\n", + "#### [Invoke directly with args](/docs/concepts/tools)\n", + "\n", + "The `DappierRealTimeSearchTool` takes a single \"query\" argument, which should be a natural language query:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 53 + }, + "id": "ASCcnvUCdIvz", + "outputId": "91538fac-f515-4a8e-adb6-0a7aa42f704c" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "string" + }, + "text/plain": [ + "\"At the last Wimbledon in 2024, Carlos Alcaraz won the title by defeating Novak Djokovic. This victory marked Alcaraz's fourth Grand Slam title at just 21 years old! 🎉🏆🎾\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.invoke({\"query\": \"What happened at the last wimbledon\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Jcbi44TRdL3D" + }, + "source": [ + "### [Invoke with ToolCall](/docs/concepts/tools)\n", + "\n", + "We can also invoke the tool with a model-generated ToolCall, in which case a ToolMessage will be returned:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "FCTpjujpdQst", + "outputId": "e184c25b-0089-4896-fbb4-1fbe09ea2f6b" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Euro 2024 is being hosted by Germany! 🇩🇪 The tournament runs from June 14 to July 14, 2024, featuring 24 teams competing across various cities like Berlin and Munich. It's going to be an exciting summer of football! ⚽️🏆\n" + ] + } + ], + "source": [ + "# This is usually generated by a model, but we'll create a tool call directly for demo purposes.\n", + "model_generated_tool_call = {\n", + " \"args\": {\"query\": \"euro 2024 host nation\"},\n", + " \"id\": \"1\",\n", + " \"name\": \"dappier\",\n", + " \"type\": \"tool_call\",\n", + "}\n", + "tool_msg = tool.invoke(model_generated_tool_call)\n", + "\n", + "# The content is a JSON string of results\n", + "print(tool_msg.content[:400])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PTBiq_2JdfjK" + }, + "source": [ + "### Chaining\n", + "\n", + "We can use our tool in a chain by first binding it to a [tool-calling model](/docs/how_to/tool_calling/) and then calling it:\n", + "\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "id": "_XImV9NtdoJq" + }, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "# !pip install -qU langchain langchain-openai\n", + "from langchain.chat_models import init_chat_model\n", + "\n", + "llm = init_chat_model(model=\"gpt-4o\", model_provider=\"openai\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "i5P5DgJOdwPI", + "outputId": "70e14f71-637e-422d-80ac-62e93b3686a9" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=\"Barbora Krejčíková won the women's singles title at Wimbledon 2024, defeating Jasmine Paolini in the final with a score of 6–2, 2–6, 6–4. This victory marked her first Wimbledon singles title and her second major singles title overall! 🎉🏆🎾\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 69, 'prompt_tokens': 222, 'total_tokens': 291, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_4691090a87', 'finish_reason': 'stop', 'logprobs': None}, id='run-87a385dd-103b-4344-a3be-2d6fd1dcfdf5-0', usage_metadata={'input_tokens': 222, 'output_tokens': 69, 'total_tokens': 291, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import datetime\n", + "\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnableConfig, chain\n", + "\n", + "today = datetime.datetime.today().strftime(\"%D\")\n", + "prompt = ChatPromptTemplate(\n", + " [\n", + " (\"system\", f\"You are a helpful assistant. The date today is {today}.\"),\n", + " (\"human\", \"{user_input}\"),\n", + " (\"placeholder\", \"{messages}\"),\n", + " ]\n", + ")\n", + "\n", + "# specifying tool_choice will force the model to call this tool.\n", + "llm_with_tools = llm.bind_tools([tool])\n", + "\n", + "llm_chain = prompt | llm_with_tools\n", + "\n", + "\n", + "@chain\n", + "def tool_chain(user_input: str, config: RunnableConfig):\n", + " input_ = {\"user_input\": user_input}\n", + " ai_msg = llm_chain.invoke(input_, config=config)\n", + " tool_msgs = tool.batch(ai_msg.tool_calls, config=config)\n", + " return llm_chain.invoke({**input_, \"messages\": [ai_msg, *tool_msgs]}, config=config)\n", + "\n", + "\n", + "tool_chain.invoke(\"who won the last womens singles wimbledon\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TycbUKZsfRQy" + }, + "source": [ + "## DappierAIRecommendationTool\n", + "\n", + "Supercharge your AI applications with Dappier's pre-trained RAG models and natural language APIs, delivering factual and up-to-date responses from premium content providers across verticals like News, Finance, Sports, Weather, and more." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "x1XfwHzHfvUN" + }, + "source": [ + "### Instantiation\n", + "\n", + "- data_model_id: str \n", + " The data model ID to use for recommendations. Data model IDs always start with the prefix \"dm_\". Defaults to \"dm_01j0pb465keqmatq9k83dthx34\". \n", + " Multiple data model IDs are available, which can be found at [Dappier marketplace](https://marketplace.dappier.com/marketplace). \n", + "\n", + "- similarity_top_k: int \n", + " The number of top documents to retrieve based on similarity. Defaults to \"9\". \n", + "\n", + "- ref: Optional[str]\n", + " The site domain where AI recommendations should be displayed. Defaults to \"None\". \n", + "\n", + "- num_articles_ref: int\n", + " The minimum number of articles to return from the specified reference domain (\"ref\"). The remaining articles will come from other sites in the RAG model. Defaults to \"0\". \n", + "\n", + "- search_algorithm: Literal[\"most_recent\", \"semantic\", \"most_recent_semantic\", \"trending\"]\n", + " The search algorithm to use for retrieving articles. Defaults to \"most_recent\". " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "-t9rS-TBhNss" + }, + "outputs": [], + "source": [ + "from langchain_dappier import DappierAIRecommendationTool\n", + "\n", + "tool = DappierAIRecommendationTool(\n", + " data_model_id=\"dm_01j0pb465keqmatq9k83dthx34\",\n", + " similarity_top_k=3,\n", + " ref=\"sportsnaut.com\",\n", + " num_articles_ref=2,\n", + " search_algorithm=\"most_recent\",\n", + " # name=\"...\", # overwrite default tool name\n", + " # description=\"...\", # overwrite default tool description\n", + " # args_schema=..., # overwrite default args_schema: BaseModel\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ad3anWusg9BI" + }, + "source": [ + "### Invocation\n", + "\n", + "#### [Invoke directly with args](/docs/concepts/tools)\n", + "\n", + "The `DappierAIRecommendationTool` takes a single \"query\" argument, which should be a natural language query:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "UQ08UkTMhI17", + "outputId": "5fd145b8-a547-4caa-ba06-ab0bfac3b104" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'author': 'Matt Weaver',\n", + " 'image_url': 'https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34/Screenshot_20250117_021643_Gallery_.jpg?width=428&height=321',\n", + " 'pubdate': 'Fri, 17 Jan 2025 08:04:03 +0000',\n", + " 'source_url': 'https://sportsnaut.com/chili-bowl-thursday-bell-column/',\n", + " 'summary': \"The article highlights the thrilling unpredictability of the Chili Bowl Midget Nationals, focusing on the dramatic shifts in fortune for drivers like Christopher Bell, Tanner Thorson, and Karter Sarff during Thursday's events. Key moments included Sarff's unfortunate pull-off and a last-lap crash that allowed Ryan Bernal to capitalize and improve his standing, showcasing the chaotic nature of the race and the importance of strategy and luck.\\n\\nAs the competition intensifies leading up to Championship Saturday, Bell faces the challenge of racing from a Last Chance Race, reflecting on the excitement and difficulties of the sport. The article emphasizes the emotional highs and lows experienced by racers, with insights from Bell and Bernal on the unpredictable nature of racing. Overall, it captures the camaraderie and passion that define the Chili Bowl, illustrating how each moment contributes to the event's narrative.\",\n", + " 'title': 'Thursday proves why every lap of Chili Bowl is so consequential'},\n", + " {'author': 'Matt Higgins',\n", + " 'image_url': 'https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34/Pete-Alonso-24524027_.jpg?width=428&height=321',\n", + " 'pubdate': 'Fri, 17 Jan 2025 02:48:42 +0000',\n", + " 'source_url': 'https://sportsnaut.com/new-york-mets-news-pete-alonso-rejected-last-ditch-contract-offer/',\n", + " 'summary': \"The New York Mets are likely parting ways with star first baseman Pete Alonso after failing to finalize a contract agreement. Alonso rejected a last-minute three-year offer worth between $68 and $70 million, leading the Mets to redirect funds towards acquiring a top reliever. With Alonso's free-agent options dwindling, speculation arises about his potential signing with another team for the 2025 season, while the Mets plan to shift Mark Vientos to first base.\\n\\nIn a strategic move, the Mets are also considering a trade for Toronto Blue Jays' star first baseman Vladimir Guerrero Jr. This potential acquisition aims to enhance the Mets' competitiveness as they reshape their roster. Guerrero's impressive offensive stats make him a valuable target, and discussions are in the early stages. Fans and analysts are keenly watching the situation, as a trade involving such a prominent player could significantly impact both teams.\",\n", + " 'title': 'MLB insiders reveal New York Mets’ last-ditch contract offer that Pete Alonso rejected'},\n", + " {'author': 'Jim Cerny',\n", + " 'image_url': 'https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34/NHL-New-York-Rangers-at-Utah-25204492_.jpg?width=428&height=321',\n", + " 'pubdate': 'Fri, 17 Jan 2025 05:10:39 +0000',\n", + " 'source_url': 'https://www.foreverblueshirts.com/new-york-rangers-news/stirring-5-3-comeback-win-utah-close-road-trip/',\n", + " 'summary': \"The New York Rangers achieved a thrilling 5-3 comeback victory against the Utah Hockey Club, showcasing their resilience after a prior overtime loss. The Rangers scored three unanswered goals in the third period, with key contributions from Reilly Smith, Chris Kreider, and Artemi Panarin, who sealed the win with an empty-net goal. This victory marked their first win of the season when trailing after two periods and capped off a successful road trip, improving their record to 21-20-3.\\n\\nIgor Shesterkin's strong performance in goal, along with Arthur Kaliyev's first goal for the team, helped the Rangers overcome an early deficit. The game featured multiple lead changes, highlighting the competitive nature of both teams. As the Rangers prepare for their next game against the Columbus Blue Jackets, they aim to close the gap in the playoff race, with the Blue Jackets currently holding a five-point lead in the Eastern Conference standings.\",\n", + " 'title': 'Rangers score 3 times in 3rd period for stirring 5-3 comeback win against Utah to close road trip'}]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.invoke({\"query\": \"latest sports news\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dzorKILbiOyy" + }, + "source": [ + "### [Invoke with ToolCall](/docs/concepts/tools)\n", + "\n", + "We can also invoke the tool with a model-generated ToolCall, in which case a ToolMessage will be returned:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "wUu-awo0iP3P", + "outputId": "af1a9679-06ae-4432-a49f-769330c1e32f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{\"author\": \"Matt Johnson\", \"image_url\": \"https://images.dappier.com/dm_01j0pb465keqmatq9k83dthx34/MLB-New-York-Mets-at-Colorado-Rockies-23948644_.jpg?width=428&height=321\", \"pubdate\": \"Fri, 17 Jan 2025 13:31:02 +0000\", \"source_url\": \"https://sportsnaut.com/new-york-mets-rumors-vladimir-guerrero-jr-news/\", \"summary\": \"The New York Mets are refocusing their strategy after failing to extend a contra\n" + ] + } + ], + "source": [ + "# This is usually generated by a model, but we'll create a tool call directly for demo purposes.\n", + "model_generated_tool_call = {\n", + " \"args\": {\"query\": \"top 3 news articles\"},\n", + " \"id\": \"1\",\n", + " \"name\": \"dappier\",\n", + " \"type\": \"tool_call\",\n", + "}\n", + "tool_msg = tool.invoke(model_generated_tool_call)\n", + "\n", + "# The content is a JSON string of results\n", + "print(tool_msg.content[:400])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "quFltDcDd2T8" + }, + "source": [ + "## API reference\n", + "\n", + "For detailed documentation of all DappierRealTimeSearchTool features and configurations head to the [API reference](https://python.langchain.com/api_reference/community/tools/langchain_dappier.tools.dappier.tool.DappierRealTimeSearchTool.html)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 1cd4d8d101a807f1bd064b0b372d132de31687f6 Mon Sep 17 00:00:00 2001 From: TheSongg <145535169+TheSongg@users.noreply.github.com> Date: Sat, 18 Jan 2025 09:31:59 +0800 Subject: [PATCH 47/47] [langchain_community.llms.xinference]: Rewrite _stream() method and support stream() method in xinference.py (#29259) - [ ] **PR title**:[langchain_community.llms.xinference]: Rewrite _stream() method and support stream() method in xinference.py - [ ] **PR message**: Rewrite the _stream method so that the chain.stream() can be used to return data streams. chain = prompt | llm chain.stream(input=user_input) - [ ] **tests**: from langchain_community.llms import Xinference from langchain.prompts import PromptTemplate llm = Xinference( server_url="http://0.0.0.0:9997", # replace your xinference server url model_uid={model_uid} # replace model_uid with the model UID return from launching the model stream = True ) prompt = PromptTemplate(input=['country'], template="Q: where can we visit in the capital of {country}? A:") chain = prompt | llm chain.stream(input={'country': 'France'}) --- .../langchain_community/llms/xinference.py | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/llms/xinference.py b/libs/community/langchain_community/llms/xinference.py index ada9f8b1f1084..06bc1c9b30e62 100644 --- a/libs/community/langchain_community/llms/xinference.py +++ b/libs/community/langchain_community/llms/xinference.py @@ -1,7 +1,20 @@ -from typing import TYPE_CHECKING, Any, Dict, Generator, List, Mapping, Optional, Union +from __future__ import annotations + +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generator, + Iterator, + List, + Mapping, + Optional, + Union, +) from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.language_models.llms import LLM +from langchain_core.outputs import GenerationChunk if TYPE_CHECKING: from xinference.client import RESTfulChatModelHandle, RESTfulGenerateModelHandle @@ -73,6 +86,26 @@ class Xinference(LLM): generate_config={"max_tokens": 1024, "stream": True}, ) + Example: + + .. code-block:: python + + from langchain_community.llms import Xinference + from langchain.prompts import PromptTemplate + + llm = Xinference( + server_url="http://0.0.0.0:9997", + model_uid={model_uid}, # replace model_uid with the model UID return from launching the model + stream=True + ) + prompt = PromptTemplate( + input=['country'], + template="Q: where can we visit in the capital of {country}? A:" + ) + chain = prompt | llm + chain.stream(input={'country': 'France'}) + + To view all the supported builtin models, run: .. code-block:: bash @@ -216,3 +249,59 @@ def _stream_generate( token=token, verbose=self.verbose, log_probs=log_probs ) yield token + + def _stream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[GenerationChunk]: + generate_config = kwargs.get("generate_config", {}) + generate_config = {**self.model_kwargs, **generate_config} + if stop: + generate_config["stop"] = stop + for stream_resp in self._create_generate_stream(prompt, generate_config): + if stream_resp: + chunk = self._stream_response_to_generation_chunk(stream_resp) + if run_manager: + run_manager.on_llm_new_token( + chunk.text, + verbose=self.verbose, + ) + yield chunk + + def _create_generate_stream( + self, prompt: str, generate_config: Optional[Dict[str, List[str]]] = None + ) -> Iterator[str]: + if self.client is None: + raise ValueError("Client is not initialized!") + model = self.client.get_model(self.model_uid) + yield from model.generate(prompt=prompt, generate_config=generate_config) + + @staticmethod + def _stream_response_to_generation_chunk( + stream_response: str, + ) -> GenerationChunk: + """Convert a stream response to a generation chunk.""" + token = "" + if isinstance(stream_response, dict): + choices = stream_response.get("choices", []) + if choices: + choice = choices[0] + if isinstance(choice, dict): + token = choice.get("text", "") + + return GenerationChunk( + text=token, + generation_info=dict( + finish_reason=choice.get("finish_reason", None), + logprobs=choice.get("logprobs", None), + ), + ) + else: + raise TypeError("choice type error!") + else: + return GenerationChunk(text=token) + else: + raise TypeError("stream_response type error!")