From 0fa06732b757c4dfb2e902dc9111ea3655fba6d5 Mon Sep 17 00:00:00 2001 From: Nir Kopler <85127941+nirkopler@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:36:54 +0200 Subject: [PATCH 001/215] community: add new gpt-3.5-turbo-1106 finetuned for cost calculation (#16039) **Description:** Added the new gpt-3.5-turbo-1106 for **finetuned** cost calculation, **Issue:** no issue found open By the information in OpenAI the pricing is the same as the older model (0613) --- libs/community/langchain_community/callbacks/openai_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/community/langchain_community/callbacks/openai_info.py b/libs/community/langchain_community/callbacks/openai_info.py index 58cb6aab3b8ac..d18059c119341 100644 --- a/libs/community/langchain_community/callbacks/openai_info.py +++ b/libs/community/langchain_community/callbacks/openai_info.py @@ -68,10 +68,12 @@ "babbage-002-finetuned": 0.0016, "davinci-002-finetuned": 0.012, "gpt-3.5-turbo-0613-finetuned": 0.012, + "gpt-3.5-turbo-1106-finetuned": 0.012, # Fine Tuned output "babbage-002-finetuned-completion": 0.0016, "davinci-002-finetuned-completion": 0.012, "gpt-3.5-turbo-0613-finetuned-completion": 0.016, + "gpt-3.5-turbo-1106-finetuned-completion": 0.016, # Azure Fine Tuned input "babbage-002-azure-finetuned": 0.0004, "davinci-002-azure-finetuned": 0.002, From f7706637a8844eea02d5f6826d7c86ff7029d524 Mon Sep 17 00:00:00 2001 From: Mahad <56235065+Mahad-lab@users.noreply.github.com> Date: Mon, 15 Jan 2024 21:37:03 +0500 Subject: [PATCH 002/215] docs: fix documentation broken link in integrations chroma (#16041) - **Description:** Fixed broken link in the documentation for Chroma., - **Issue:** - **Dependencies:** --- docs/docs/integrations/providers/chroma.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/providers/chroma.mdx b/docs/docs/integrations/providers/chroma.mdx index 76c2f74244f90..77a914471c774 100644 --- a/docs/docs/integrations/providers/chroma.mdx +++ b/docs/docs/integrations/providers/chroma.mdx @@ -22,7 +22,7 @@ For a more detailed walkthrough of the Chroma wrapper, see [this notebook](/docs ## Retriever -See a [usage example](/docs/integrations/retrievers/self_query/chroma). +See a [usage example](/docs/integrations/retrievers/self_query/chroma_self_query). ```python from langchain.retrievers import SelfQueryRetriever From 60d6a416e6a9e02755f4d24194fbd128d50b4c94 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 15 Jan 2024 10:09:20 -0800 Subject: [PATCH 003/215] docs: fix self query diagram (#16043) --- .../retrievers/self_query.ipynb | 4 ++-- docs/static/img/self_querying.jpg | Bin 96014 -> 75012 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/modules/data_connection/retrievers/self_query.ipynb b/docs/docs/modules/data_connection/retrievers/self_query.ipynb index 22ab1e46fa67e..2b44db886f0ae 100644 --- a/docs/docs/modules/data_connection/retrievers/self_query.ipynb +++ b/docs/docs/modules/data_connection/retrievers/self_query.ipynb @@ -15,7 +15,7 @@ "\n", "A self-querying retriever is one that, as the name suggests, has the ability to query itself. Specifically, given any natural language query, the retriever uses a query-constructing LLM chain to write a structured query and then applies that structured query to its underlying VectorStore. This allows the retriever to not only use the user-input query for semantic similarity comparison with the contents of stored documents but to also extract filters from the user query on the metadata of stored documents and to execute those filters.\n", "\n", - "![](https://drive.google.com/uc?id=1OQUN-0MJcDUxmPXofgS7MqReEs720pqS)\n", + "![](../../../../static/img/self_querying.jpg)\n", "\n", "## Get started\n", "For demonstration purposes we'll use a `Chroma` vector store. We've created a small demo set of documents that contain summaries of movies.\n", @@ -561,7 +561,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.9.1" } }, "nbformat": 4, diff --git a/docs/static/img/self_querying.jpg b/docs/static/img/self_querying.jpg index 7970423415d7ad04db89749efb689b55b733b55b..1f3055b53bdb49fdeed8242062dcf19de13e93bc 100644 GIT binary patch literal 75012 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 literal 96014 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 TXCCy@cqD<+UGmE{|K9`vlb{4f From 86321a949f06a41a286a204f447de8491facbeaa Mon Sep 17 00:00:00 2001 From: shahrin014 <shahrin14@gmail.com> Date: Tue, 16 Jan 2024 03:17:58 +0900 Subject: [PATCH 004/215] community: Ollama - Parameter structure to follow official documentation (#16035) ## Feature - Follow parameter structure as per official documentation - top level parameters (e.g. model, system, template) will be passed as top level parameters - other parameters will be sent in options unless options is provided ![image](https://github.com/langchain-ai/langchain/assets/17451563/d14715d9-9701-4ee3-b44b-89fffea62389) ## Tests - Test if top level parameters handled properly - Test if parameters that are not top level parameters are handled as options - Test if options is provided, it will be passed as is --- .../langchain_community/llms/ollama.py | 14 +- .../tests/unit_tests/llms/test_ollama.py | 133 +++++++++++++++++- 2 files changed, 137 insertions(+), 10 deletions(-) diff --git a/libs/community/langchain_community/llms/ollama.py b/libs/community/langchain_community/llms/ollama.py index 4e7f1838f75d6..078edcf092885 100644 --- a/libs/community/langchain_community/llms/ollama.py +++ b/libs/community/langchain_community/llms/ollama.py @@ -190,8 +190,9 @@ def _create_stream( params = self._default_params - if "model" in kwargs: - params["model"] = kwargs["model"] + for key in self._default_params: + if key in kwargs: + params[key] = kwargs[key] if "options" in kwargs: params["options"] = kwargs["options"] @@ -199,7 +200,7 @@ def _create_stream( params["options"] = { **params["options"], "stop": stop, - **kwargs, + **{k: v for k, v in kwargs.items() if k not in self._default_params}, } if payload.get("messages"): @@ -253,8 +254,9 @@ async def _acreate_stream( params = self._default_params - if "model" in kwargs: - params["model"] = kwargs["model"] + for key in self._default_params: + if key in kwargs: + params[key] = kwargs[key] if "options" in kwargs: params["options"] = kwargs["options"] @@ -262,7 +264,7 @@ async def _acreate_stream( params["options"] = { **params["options"], "stop": stop, - **kwargs, + **{k: v for k, v in kwargs.items() if k not in self._default_params}, } if payload.get("messages"): diff --git a/libs/community/tests/unit_tests/llms/test_ollama.py b/libs/community/tests/unit_tests/llms/test_ollama.py index ff18daacfa64c..bf2229b4fcdc7 100644 --- a/libs/community/tests/unit_tests/llms/test_ollama.py +++ b/libs/community/tests/unit_tests/llms/test_ollama.py @@ -31,7 +31,7 @@ def test_pass_headers_if_provided(monkeypatch: MonkeyPatch) -> None: timeout=300, ) - def mockPost(url, headers, json, stream, timeout): + def mock_post(url, headers, json, stream, timeout): assert url == "https://ollama-hostname:8000/api/generate/" assert headers == { "Content-Type": "application/json", @@ -44,7 +44,7 @@ def mockPost(url, headers, json, stream, timeout): return mock_response_stream() - monkeypatch.setattr(requests, "post", mockPost) + monkeypatch.setattr(requests, "post", mock_post) llm("Test prompt") @@ -52,7 +52,7 @@ def mockPost(url, headers, json, stream, timeout): def test_handle_if_headers_not_provided(monkeypatch: MonkeyPatch) -> None: llm = Ollama(base_url="https://ollama-hostname:8000", model="foo", timeout=300) - def mockPost(url, headers, json, stream, timeout): + def mock_post(url, headers, json, stream, timeout): assert url == "https://ollama-hostname:8000/api/generate/" assert headers == { "Content-Type": "application/json", @@ -63,6 +63,131 @@ def mockPost(url, headers, json, stream, timeout): return mock_response_stream() - monkeypatch.setattr(requests, "post", mockPost) + monkeypatch.setattr(requests, "post", mock_post) llm("Test prompt") + + +def test_handle_kwargs_top_level_parameters(monkeypatch: MonkeyPatch) -> None: + """Test that top level params are sent to the endpoint as top level params""" + llm = Ollama(base_url="https://ollama-hostname:8000", model="foo", timeout=300) + + def mock_post(url, headers, json, stream, timeout): + assert url == "https://ollama-hostname:8000/api/generate/" + assert headers == { + "Content-Type": "application/json", + } + assert json == { + "format": None, + "images": None, + "model": "test-model", + "options": { + "mirostat": None, + "mirostat_eta": None, + "mirostat_tau": None, + "num_ctx": None, + "num_gpu": None, + "num_thread": None, + "repeat_last_n": None, + "repeat_penalty": None, + "stop": [], + "temperature": None, + "tfs_z": None, + "top_k": None, + "top_p": None, + }, + "prompt": "Test prompt", + "system": "Test system prompt", + "template": None, + } + assert stream is True + assert timeout == 300 + + return mock_response_stream() + + monkeypatch.setattr(requests, "post", mock_post) + + llm("Test prompt", model="test-model", system="Test system prompt") + + +def test_handle_kwargs_with_unknown_param(monkeypatch: MonkeyPatch) -> None: + """ + Test that params that are not top level params will be sent to the endpoint + as options + """ + llm = Ollama(base_url="https://ollama-hostname:8000", model="foo", timeout=300) + + def mock_post(url, headers, json, stream, timeout): + assert url == "https://ollama-hostname:8000/api/generate/" + assert headers == { + "Content-Type": "application/json", + } + assert json == { + "format": None, + "images": None, + "model": "foo", + "options": { + "mirostat": None, + "mirostat_eta": None, + "mirostat_tau": None, + "num_ctx": None, + "num_gpu": None, + "num_thread": None, + "repeat_last_n": None, + "repeat_penalty": None, + "stop": [], + "temperature": 0.8, + "tfs_z": None, + "top_k": None, + "top_p": None, + "unknown": "Unknown parameter value", + }, + "prompt": "Test prompt", + "system": None, + "template": None, + } + assert stream is True + assert timeout == 300 + + return mock_response_stream() + + monkeypatch.setattr(requests, "post", mock_post) + + llm("Test prompt", unknown="Unknown parameter value", temperature=0.8) + + +def test_handle_kwargs_with_options(monkeypatch: MonkeyPatch) -> None: + """ + Test that if options provided it will be sent to the endpoint as options, + ignoring other params that are not top level params. + """ + llm = Ollama(base_url="https://ollama-hostname:8000", model="foo", timeout=300) + + def mock_post(url, headers, json, stream, timeout): + assert url == "https://ollama-hostname:8000/api/generate/" + assert headers == { + "Content-Type": "application/json", + } + assert json == { + "format": None, + "images": None, + "model": "test-another-model", + "options": {"unknown_option": "Unknown option value"}, + "prompt": "Test prompt", + "system": None, + "template": None, + } + assert stream is True + assert timeout == 300 + + return mock_response_stream() + + monkeypatch.setattr(requests, "post", mock_post) + + llm( + "Test prompt", + model="test-another-model", + options={"unknown_option": "Unknown option value"}, + unknown="Unknown parameter value", + temperature=0.8, + ) From 768e5e33bc9951d351a661cda1b1d59a394fd205 Mon Sep 17 00:00:00 2001 From: Karim Lalani <jimmy00784@gmail.com> Date: Mon, 15 Jan 2024 12:31:59 -0600 Subject: [PATCH 005/215] community[minor]: Fix to match SurrealDB 0.3.2 SDK (#15996) New version of SurrealDB python sdk was causing the integration to break. This fix addresses that change. --- .../integrations/vectorstores/surrealdb.ipynb | 53 ++++++++++++------- .../vectorstores/surrealdb.py | 22 +++++--- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/docs/docs/integrations/vectorstores/surrealdb.ipynb b/docs/docs/integrations/vectorstores/surrealdb.ipynb index 3d5a0defdc5a2..1542ae75032aa 100644 --- a/docs/docs/integrations/vectorstores/surrealdb.ipynb +++ b/docs/docs/integrations/vectorstores/surrealdb.ipynb @@ -40,7 +40,7 @@ }, "outputs": [], "source": [ - "%pip install --upgrade --quiet surrealdb langchain langchain-community" + "# %pip install --upgrade --quiet surrealdb langchain langchain-community" ] }, { @@ -54,6 +54,19 @@ { "cell_type": "code", "execution_count": 1, + "id": "1c2d942d-5d90-4f9f-af96-dff976e4510f", + "metadata": {}, + "outputs": [], + "source": [ + "# add this import for running in jupyter notebook\n", + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, "id": "e49be085-ddf1-4028-8c0c-97836ce4a873", "metadata": { "tags": [] @@ -68,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "38222aee-adc5-44c2-913c-97977b394cf5", "metadata": { "tags": [] @@ -92,28 +105,28 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "ff9d0304-1e11-4db2-9454-1350db7907e6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['documents:th7j29cjsx6495wluo7e',\n", - " 'documents:qkqhhjnl7ahbhr07euky',\n", - " 'documents:8kd6xw8o7y0l171iqry0',\n", - " 'documents:33ejf42dlkmavol9si74',\n", - " 'documents:f7y4dbs7eitqz58xt1p5']" + "['documents:38hz49bv1p58f5lrvrdc',\n", + " 'documents:niayw63vzwm2vcbh6w2s',\n", + " 'documents:it1fa3ktplbuye43n0ch',\n", + " 'documents:il8f7vgbbp9tywmsn98c',\n", + " 'documents:vza4c6cqje0avqd58gal']" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "db = SurrealDBStore(\n", - " dburl=\"http://localhost:8000/rpc\", # url for the hosted SurrealDB database\n", + " dburl=\"ws://localhost:8000/rpc\", # url for the hosted SurrealDB database\n", " embedding_function=embeddings,\n", " db_user=\"root\", # SurrealDB credentials if needed: db username\n", " db_pass=\"root\", # SurrealDB credentials if needed: db password\n", @@ -145,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "73d66563-4e1f-4edf-9e95-5fc9adcfa2cb", "metadata": {}, "outputs": [], @@ -153,7 +166,7 @@ "await db.adelete()\n", "\n", "db = await SurrealDBStore.afrom_documents(\n", - " dburl=\"http://localhost:8000/rpc\", # url for the hosted SurrealDB database\n", + " dburl=\"ws://localhost:8000/rpc\", # url for the hosted SurrealDB database\n", " embedding=embeddings,\n", " documents=docs,\n", " db_user=\"root\", # SurrealDB credentials if needed: db username\n", @@ -174,7 +187,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "aa28a7f8-41d0-4299-84eb-91d1576e8a63", "metadata": { "tags": [] @@ -187,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "1eb16d2a-b466-456a-b412-5e74bb8523dd", "metadata": { "tags": [] @@ -229,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "8e9eef05-1516-469a-ad36-880c69aef7a9", "metadata": { "tags": [] @@ -241,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "bd5fb0e4-2a94-4bb4-af8a-27327ecb1a7f", "metadata": { "tags": [] @@ -250,11 +263,11 @@ { "data": { "text/plain": [ - "(Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \\n\\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \\n\\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \\n\\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'id': 'documents:639m99rzwqlm9imcwg13'}),\n", - " 0.39839545290036454)" + "(Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \\n\\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \\n\\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \\n\\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'id': 'documents:slgdlhjkfknhqo15xz0w', 'source': '../../modules/state_of_the_union.txt'}),\n", + " 0.39839531721941895)" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -280,7 +293,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/libs/community/langchain_community/vectorstores/surrealdb.py b/libs/community/langchain_community/vectorstores/surrealdb.py index 951ec04463874..d21f5bf0e0200 100644 --- a/libs/community/langchain_community/vectorstores/surrealdb.py +++ b/libs/community/langchain_community/vectorstores/surrealdb.py @@ -55,14 +55,25 @@ def __init__( embedding_function: Embeddings, **kwargs: Any, ) -> None: - from surrealdb import Surreal + try: + from surrealdb import Surreal + except ImportError as e: + raise ImportError( + """Cannot import from surrealdb. + please install with `pip install surrealdb`.""" + ) from e + + self.dburl = kwargs.pop("dburl", "ws://localhost:8000/rpc") + + if self.dburl[0:2] == "ws": + self.sdb = Surreal(self.dburl) + else: + raise ValueError("Only websocket connections are supported at this time.") - self.collection = kwargs.pop("collection", "documents") self.ns = kwargs.pop("ns", "langchain") self.db = kwargs.pop("db", "database") - self.dburl = kwargs.pop("dburl", "ws://localhost:8000/rpc") + self.collection = kwargs.pop("collection", "documents") self.embedding_function = embedding_function - self.sdb = Surreal(self.dburl) self.kwargs = kwargs async def initialize(self) -> None: @@ -70,12 +81,11 @@ async def initialize(self) -> None: Initialize connection to surrealdb database and authenticate if credentials are provided """ - await self.sdb.connect(self.dburl) + await self.sdb.connect() if "db_user" in self.kwargs and "db_pass" in self.kwargs: user = self.kwargs.get("db_user") password = self.kwargs.get("db_pass") await self.sdb.signin({"user": user, "pass": password}) - await self.sdb.use(self.ns, self.db) @property From 14244bd7e5a54d26fc8f180ee0fc106bcd05a96b Mon Sep 17 00:00:00 2001 From: Karim Lalani <jimmy00784@gmail.com> Date: Mon, 15 Jan 2024 12:32:42 -0600 Subject: [PATCH 006/215] community[minor]: Added document loader for SurrealDB (#15995) Added a simple document loader to work with SurrealDB. --- .../document_loaders/surrealdb.ipynb | 236 ++++++++++++++++++ .../document_loaders/__init__.py | 2 + .../document_loaders/surrealdb.py | 97 +++++++ .../document_loaders/test_imports.py | 1 + 4 files changed, 336 insertions(+) create mode 100644 docs/docs/integrations/document_loaders/surrealdb.ipynb create mode 100644 libs/community/langchain_community/document_loaders/surrealdb.py diff --git a/docs/docs/integrations/document_loaders/surrealdb.ipynb b/docs/docs/integrations/document_loaders/surrealdb.ipynb new file mode 100644 index 0000000000000..e8e68effb978b --- /dev/null +++ b/docs/docs/integrations/document_loaders/surrealdb.ipynb @@ -0,0 +1,236 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5812b612-3e77-4be2-aefb-fbb16141ab79", + "metadata": {}, + "source": [ + "# SurrealDB\n", + "\n", + ">[SurrealDB](https://surrealdb.com/) is an end-to-end cloud-native database designed for modern applications, including web, mobile, serverless, Jamstack, backend, and traditional applications. With SurrealDB, you can simplify your database and API infrastructure, reduce development time, and build secure, performant apps quickly and cost-effectively.\n", + ">\n", + ">**Key features of SurrealDB include:**\n", + ">\n", + ">* **Reduces development time:** SurrealDB simplifies your database and API stack by removing the need for most server-side components, allowing you to build secure, performant apps faster and cheaper.\n", + ">* **Real-time collaborative API backend service:** SurrealDB functions as both a database and an API backend service, enabling real-time collaboration.\n", + ">* **Support for multiple querying languages:** SurrealDB supports SQL querying from client devices, GraphQL, ACID transactions, WebSocket connections, structured and unstructured data, graph querying, full-text indexing, and geospatial querying.\n", + ">* **Granular access control:** SurrealDB provides row-level permissions-based access control, giving you the ability to manage data access with precision.\n", + ">\n", + ">View the [features](https://surrealdb.com/features), the latest [releases](https://surrealdb.com/releases), and [documentation](https://surrealdb.com/docs).\n", + "\n", + "This notebook shows how to use functionality related to the `SurrealDBLoader`." + ] + }, + { + "cell_type": "markdown", + "id": "f56ccec5-24b3-4762-91a6-91385e041fee", + "metadata": {}, + "source": [ + "## Overview\n", + "\n", + "The SurrealDB Document Loader returns a list of Langchain Documents from a SurrealDB database.\n", + "\n", + "The Document Loader takes the following optional parameters:\n", + "\n", + "* `dburl`: connection string to the websocket endpoint. default: `ws://localhost:8000/rpc`\n", + "* `ns`: name of the namespace. default: `langchain`\n", + "* `db`: name of the database. default: `database`\n", + "* `table`: name of the table. default: `documents`\n", + "* `db_user`: SurrealDB credentials if needed: db username.\n", + "* `db_pass`: SurrealDB credentails if needed: db password.\n", + "* `filter_criteria`: dictionary to construct the `WHERE` clause for filtering results from table.\n", + "\n", + "The output `Document` takes the following shape:\n", + "```\n", + "Document(\n", + " page_content=<json encoded string containing the result document>,\n", + " metadata={\n", + " 'id': <document id>,\n", + " 'ns': <namespace name>,\n", + " 'db': <database_name>,\n", + " 'table': <table name>,\n", + " ... <additional fields from metadata property of the document>\n", + " }\n", + ")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "77b024e0-c804-4b19-9f5e-0099eb61ba79", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Uncomment the below cells to install surrealdb and langchain." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "508bc4f3-3aa2-45d3-8e59-cd7d0ffec379", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# %pip install --upgrade --quiet surrealdb langchain langchain-community" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3ee3d767-b9ba-4be4-9e80-8fa6376beaba", + "metadata": {}, + "outputs": [], + "source": [ + "# add this import for running in jupyter notebook\n", + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1ec629f4-b99a-44f1-a938-29de7439f121", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "from langchain_community.document_loaders.surrealdb import SurrealDBLoader" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8deb90ac-7d4e-422c-a87a-8e6e41390a6d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "loader = SurrealDBLoader(\n", + " dburl=\"ws://localhost:8000/rpc\",\n", + " ns=\"langchain\",\n", + " db=\"database\",\n", + " table=\"documents\",\n", + " db_user=\"root\",\n", + " db_pass=\"root\",\n", + " filter_criteria={},\n", + ")\n", + "docs = loader.load()\n", + "len(docs)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0aa9d3f7-56b3-464d-9d3d-1df7164122ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'id': 'documents:zzz434sa584xl3b4ohvk',\n", + " 'source': '../../modules/state_of_the_union.txt',\n", + " 'ns': 'langchain',\n", + " 'db': 'database',\n", + " 'table': 'documents'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "doc = docs[-1]\n", + "doc.metadata" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0378dd34-c690-4b8e-8816-90a8acc2f227", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "18078" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(doc.page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f30f1141-329b-4674-acb4-36d9d5a9ef0a", + "metadata": {}, + "outputs": [], + "source": [ + "page_content = json.loads(doc.page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2a58496f-a831-40ec-be6b-92ce70f78133", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'When we use taxpayer dollars to rebuild America – we are going to Buy American: buy American products to support American jobs. \\n\\nThe federal government spends about $600 Billion a year to keep the country safe and secure. \\n\\nThere’s been a law on the books for almost a century \\nto make sure taxpayers’ dollars support American jobs and businesses. \\n\\nEvery Administration says they’ll do it, but we are actually doing it. \\n\\nWe will buy American to make sure everything from the deck of an aircraft carrier to the steel on highway guardrails are made in America. \\n\\nBut to compete for the best jobs of the future, we also need to level the playing field with China and other competitors. \\n\\nThat’s why it is so important to pass the Bipartisan Innovation Act sitting in Congress that will make record investments in emerging technologies and American manufacturing. \\n\\nLet me give you one example of why it’s so important to pass it.'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "page_content[\"text\"]" + ] + } + ], + "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" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/community/langchain_community/document_loaders/__init__.py b/libs/community/langchain_community/document_loaders/__init__.py index bf2ac63bce111..5952c4a08b37e 100644 --- a/libs/community/langchain_community/document_loaders/__init__.py +++ b/libs/community/langchain_community/document_loaders/__init__.py @@ -180,6 +180,7 @@ from langchain_community.document_loaders.spreedly import SpreedlyLoader from langchain_community.document_loaders.srt import SRTLoader from langchain_community.document_loaders.stripe import StripeLoader +from langchain_community.document_loaders.surrealdb import SurrealDBLoader from langchain_community.document_loaders.telegram import ( TelegramChatApiLoader, TelegramChatFileLoader, @@ -360,6 +361,7 @@ "SnowflakeLoader", "SpreedlyLoader", "StripeLoader", + "SurrealDBLoader", "TelegramChatApiLoader", "TelegramChatFileLoader", "TelegramChatLoader", diff --git a/libs/community/langchain_community/document_loaders/surrealdb.py b/libs/community/langchain_community/document_loaders/surrealdb.py new file mode 100644 index 0000000000000..c5df9406dbfb9 --- /dev/null +++ b/libs/community/langchain_community/document_loaders/surrealdb.py @@ -0,0 +1,97 @@ +import asyncio +import json +import logging +from typing import Any, Dict, List, Optional + +from langchain_core.documents import Document + +from langchain_community.document_loaders.base import BaseLoader + +logger = logging.getLogger(__name__) + + +class SurrealDBLoader(BaseLoader): + """Load SurrealDB documents.""" + + def __init__( + self, + filter_criteria: Optional[Dict] = None, + **kwargs: Any, + ) -> None: + try: + from surrealdb import Surreal + except ImportError as e: + raise ImportError( + """Cannot import from surrealdb. + please install with `pip install surrealdb`.""" + ) from e + + self.dburl = kwargs.pop("dburl", "ws://localhost:8000/rpc") + + if self.dburl[0:2] == "ws": + self.sdb = Surreal(self.dburl) + else: + raise ValueError("Only websocket connections are supported at this time.") + + self.filter_criteria = filter_criteria or {} + + if "table" in self.filter_criteria: + raise ValueError( + "key `table` is not a valid criteria for `filter_criteria` argument." + ) + + self.ns = kwargs.pop("ns", "langchain") + self.db = kwargs.pop("db", "database") + self.table = kwargs.pop("table", "documents") + self.sdb = Surreal(self.dburl) + self.kwargs = kwargs + + asyncio.run(self.initialize()) + + async def initialize(self) -> None: + """ + Initialize connection to surrealdb database + and authenticate if credentials are provided + """ + await self.sdb.connect() + if "db_user" in self.kwargs and "db_pass" in self.kwargs: + user = self.kwargs.get("db_user") + password = self.kwargs.get("db_pass") + await self.sdb.signin({"user": user, "pass": password}) + + await self.sdb.use(self.ns, self.db) + + def load(self) -> List[Document]: + async def _load() -> List[Document]: + await self.initialize() + return await self.aload() + + return asyncio.run(_load()) + + async def aload(self) -> List[Document]: + """Load data into Document objects.""" + + query = "SELECT * FROM type::table($table)" + if self.filter_criteria is not None and len(self.filter_criteria) > 0: + query += " WHERE " + for idx, key in enumerate(self.filter_criteria): + query += f""" {"AND" if idx > 0 else ""} {key} = ${key}""" + + metadata = { + "ns": self.ns, + "db": self.db, + "table": self.table, + } + results = await self.sdb.query( + query, {"table": self.table, **self.filter_criteria} + ) + + return [ + ( + Document( + page_content=json.dumps(result), + metadata={"id": result["id"], **result["metadata"], **metadata}, + ) + ) + for result in results[0]["result"] + ] diff --git a/libs/community/tests/unit_tests/document_loaders/test_imports.py b/libs/community/tests/unit_tests/document_loaders/test_imports.py index d730f6bfc19a3..43758fc16540d 100644 --- a/libs/community/tests/unit_tests/document_loaders/test_imports.py +++ b/libs/community/tests/unit_tests/document_loaders/test_imports.py @@ -132,6 +132,7 @@ "SnowflakeLoader", "SpreedlyLoader", "StripeLoader", + "SurrealDBLoader", "TelegramChatApiLoader", "TelegramChatFileLoader", "TelegramChatLoader", From c0773ab329953e57f33000a741e3f4a2a907fdf1 Mon Sep 17 00:00:00 2001 From: Raunak <rksrivastava100@gmail.com> Date: Tue, 16 Jan 2024 00:04:10 +0530 Subject: [PATCH 007/215] community[patch]: Fixed 'coroutine' object is not subscriptable error (#15986) - **Description:** Added parenthesis in return statement of aembed_query() funtion to fix 'coroutine' object is not subscriptable error. - **Dependencies:** NA Co-authored-by: H161961 <Raunak.Raunak@Honeywell.com> --- .../community/langchain_community/embeddings/huggingface_hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/embeddings/huggingface_hub.py b/libs/community/langchain_community/embeddings/huggingface_hub.py index 465f8c69256a2..70424ac0b2181 100644 --- a/libs/community/langchain_community/embeddings/huggingface_hub.py +++ b/libs/community/langchain_community/embeddings/huggingface_hub.py @@ -144,5 +144,5 @@ async def aembed_query(self, text: str) -> List[float]: Returns: Embeddings for the text. """ - response = await self.aembed_documents([text])[0] + response = (await self.aembed_documents([text]))[0] return response From fb7e66b809a685f28b5f5b4c5885622b246d06ec Mon Sep 17 00:00:00 2001 From: Antonio Mindov <antonio.mindov@limechain.tech> Date: Mon, 15 Jan 2024 20:35:26 +0200 Subject: [PATCH 008/215] docs: fix typo in inspect runnables docs (#15994) - **Description:** Fixing a typo related to prompts in the inspecting runnables docs --- docs/docs/expression_language/how_to/inspect.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/expression_language/how_to/inspect.ipynb b/docs/docs/expression_language/how_to/inspect.ipynb index e50d976d5f214..d680530e4e628 100644 --- a/docs/docs/expression_language/how_to/inspect.ipynb +++ b/docs/docs/expression_language/how_to/inspect.ipynb @@ -177,7 +177,7 @@ "source": [ "## Get the prompts\n", "\n", - "An important part of every chain is the prompts that are used. You can get the graphs present in the chain:" + "An important part of every chain is the prompts that are used. You can get the prompts present in the chain:" ] }, { From 8799b028a6fa09dfefda92ea9a8bfc883c4e1aaf Mon Sep 17 00:00:00 2001 From: Mohammed Naqi <60170196+CsEnox@users.noreply.github.com> Date: Tue, 16 Jan 2024 00:09:25 +0530 Subject: [PATCH 009/215] community[minor]: Adding asynchronous function implementation for Doctran (#15941) ## Description In this update, I addressed the missing implementation for atransform_document, which is the asynchronous counterpart of transform_document in Doctran. ### Usage Example: ```py # Instantiate DoctranPropertyExtractor with specified properties property_extractor = DoctranPropertyExtractor(properties=properties) # Asynchronously extract properties from a list of documents extracted_document = await property_extractor.atransform_documents( documents, properties=properties ) # Display metadata of the first extracted document print(json.dumps(extracted_document[0].metadata, indent=2)) ``` ## Issue - Pull request #14525 has caused a break in the aforementioned code. Instead of removing an asynchronous implementation of a function, consider implementing a synchronous version alongside it. --- .../doctran_text_extract.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/document_transformers/doctran_text_extract.py b/libs/community/langchain_community/document_transformers/doctran_text_extract.py index eee109193eefc..e942eafdde85d 100644 --- a/libs/community/langchain_community/document_transformers/doctran_text_extract.py +++ b/libs/community/langchain_community/document_transformers/doctran_text_extract.py @@ -66,7 +66,27 @@ def __init__( async def atransform_documents( self, documents: Sequence[Document], **kwargs: Any ) -> Sequence[Document]: - raise NotImplementedError + """Extracts properties from text documents using doctran.""" + try: + from doctran import Doctran, ExtractProperty + + doctran = Doctran( + openai_api_key=self.openai_api_key, openai_model=self.openai_api_model + ) + except ImportError: + raise ImportError( + "Install doctran to use this parser. (pip install doctran)" + ) + properties = [ExtractProperty(**property) for property in self.properties] + for d in documents: + doctran_doc = ( + doctran.parse(content=d.page_content) + .extract(properties=properties) + .execute() + ) + + d.metadata["extracted_properties"] = doctran_doc.extracted_properties + return documents def transform_documents( self, documents: Sequence[Document], **kwargs: Any From ce7723c1e5ea672d9f585ffbde82edabed880cb4 Mon Sep 17 00:00:00 2001 From: Ashley Xu <139821907+ashleyxuu@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:45:15 -0700 Subject: [PATCH 010/215] community[minor]: add additional support for `BigQueryVectorSearch` (#15904) BigQuery vector search lets you use GoogleSQL to do semantic search, using vector indexes for fast but approximate results, or using brute force for exact results. This PR: 1. Add `metadata[_job_ib]` in Document returned by any similarity search 2. Add `explore_job_stats` to enable users to explore job statistics and better the debuggability 3. Set the minimum row limit for running create vector index. --- .../vectorstores/bigquery_vector_search.ipynb | 18 +++++++++++++++ .../vectorstores/bigquery_vector_search.py | 22 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb b/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb index 81f31bdae5922..29b9430871e8a 100644 --- a/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb +++ b/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb @@ -324,6 +324,24 @@ "docs = store.similarity_search_by_vector(query_vector, filter={\"len\": 6})\n", "print(docs)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Explore job satistics with BigQuery Job Id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "job_id = \"\" # @param {type:\"string\"}\n", + "# Debug and explore the job statistics with a BigQuery Job id.\n", + "store.explore_job_stats(job_id)" + ] } ], "metadata": { diff --git a/libs/community/langchain_community/vectorstores/bigquery_vector_search.py b/libs/community/langchain_community/vectorstores/bigquery_vector_search.py index d132e7e071a73..64a1f4b76551c 100644 --- a/libs/community/langchain_community/vectorstores/bigquery_vector_search.py +++ b/libs/community/langchain_community/vectorstores/bigquery_vector_search.py @@ -28,6 +28,7 @@ DEFAULT_CONTENT_COLUMN_NAME = "content" # text content, do not rename DEFAULT_TOP_K = 4 # default number of documents returned from similarity search +_MIN_INDEX_ROWS = 5000 # minimal number of rows for creating an index _INDEX_CHECK_PERIOD_SECONDS = 60 # Do not check for index more often that this. _vector_table_lock = Lock() # process-wide BigQueryVectorSearch table lock @@ -192,6 +193,11 @@ def _initialize_vector_index(self) -> Any: if self._have_index or self._creating_index: # Already have an index or in the process of creating one. return + table = self.bq_client.get_table(self.vectors_table) + if (table.num_rows or 0) < _MIN_INDEX_ROWS: + # Not enough rows to create index. + self._logger.debug("Not enough rows to create a vector index.") + return if ( datetime.utcnow() - self._last_index_check ).total_seconds() < _INDEX_CHECK_PERIOD_SECONDS: @@ -228,6 +234,10 @@ def _create_index_in_background(self): def _create_index(self): from google.api_core.exceptions import ClientError + table = self.bq_client.get_table(self.vectors_table) + if (table.num_rows or 0) < _MIN_INDEX_ROWS: + # Not enough rows to create index. + return if self.distance_strategy == DistanceStrategy.EUCLIDEAN_DISTANCE: distance_type = "EUCLIDEAN" elif self.distance_strategy == DistanceStrategy.COSINE: @@ -534,6 +544,7 @@ def _search_with_score_and_embeddings_by_vector( else: metadata = {} metadata["__id"] = row[self.doc_id_field] + metadata["__job_id"] = job.job_id doc = Document(page_content=row[self.content_field], metadata=metadata) document_tuples.append( (doc, row[self.text_embedding_field], row["_vector_search_distance"]) @@ -833,3 +844,14 @@ def from_texts( vs_obj = BigQueryVectorSearch(embedding=embedding, **kwargs) vs_obj.add_texts(texts, metadatas) return vs_obj + + def explore_job_stats(self, job_id: str) -> Dict: + """Return the statistics for a single job execution. + + Args: + job_id: The BigQuery Job id. + + Returns: + A dictionary of job statistics for a given job. + """ + return self.bq_client.get_job(job_id)._properties["statistics"] From e80aab2275cb99258d562eb0e575e02a8ff61c3b Mon Sep 17 00:00:00 2001 From: Massimiliano Pronesti <massimiliano.pronesti@gmail.com> Date: Mon, 15 Jan 2024 19:50:47 +0100 Subject: [PATCH 011/215] docs(community): update Amadeus toolkit to langchain v0.1 (#15976) - **Description:** docs update following the changes introduced in #15879 <!-- Thank you for contributing to LangChain! Please title your PR "<package>: <description>", where <package> is whichever of langchain, community, core, experimental, etc. is being modified. Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes if applicable, - **Dependencies:** any dependencies required for this change, - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` from the root of the package you've modified to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ 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. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> --- docs/docs/integrations/toolkits/amadeus.ipynb | 227 +++++++++++------- .../tools/amadeus/closest_airport.py | 2 +- 2 files changed, 145 insertions(+), 84 deletions(-) diff --git a/docs/docs/integrations/toolkits/amadeus.ipynb b/docs/docs/integrations/toolkits/amadeus.ipynb index 940b98f31eee5..e97067c0f60de 100644 --- a/docs/docs/integrations/toolkits/amadeus.ipynb +++ b/docs/docs/integrations/toolkits/amadeus.ipynb @@ -17,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -30,13 +30,18 @@ "source": [ "## Assign Environmental Variables\n", "\n", - "The toolkit will read the AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET environmental variables to authenticate the user so you need to set them here. You will also need to set your OPENAI_API_KEY to use the agent later." + "The toolkit will read the AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET environmental variables to authenticate the user, so you need to set them here. " ] }, { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-13T17:45:56.531388579Z", + "start_time": "2024-01-13T17:45:56.523533018Z" + } + }, "outputs": [], "source": [ "# Set environmental variables here\n", @@ -44,7 +49,6 @@ "\n", "os.environ[\"AMADEUS_CLIENT_ID\"] = \"CLIENT_ID\"\n", "os.environ[\"AMADEUS_CLIENT_SECRET\"] = \"CLIENT_SECRET\"\n", - "os.environ[\"OPENAI_API_KEY\"] = \"API_KEY\"\n", "# os.environ[\"AMADEUS_HOSTNAME\"] = \"production\" or \"test\"" ] }, @@ -57,11 +61,39 @@ "To start, you need to create the toolkit, so you can access its tools later." ] }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "By default, `AmadeusToolkit` uses `ChatOpenAI` to identify airports closest to a given location. To use it, just set `OPENAI_API_KEY`.\n" + ] + }, { "cell_type": "code", "execution_count": 3, "metadata": { - "tags": [] + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-01-13T17:45:56.557041160Z", + "start_time": "2024-01-13T17:45:56.530682481Z" + } + }, + "outputs": [], + "source": [ + "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_OPENAI_KEY\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [], + "ExecuteTime": { + "end_time": "2024-01-13T17:45:58.431168124Z", + "start_time": "2024-01-13T17:45:56.536269739Z" + } }, "outputs": [], "source": [ @@ -71,6 +103,35 @@ "tools = toolkit.get_tools()" ] }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "Alternatively, you can use any LLM supported by langchain, e.g. `HuggingFaceHub`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from langchain_community.llms import HuggingFaceHub\n", + "\n", + "os.environ[\"HUGGINGFACEHUB_API_TOKEN\"] = \"YOUR_HF_API_TOKEN\"\n", + "\n", + "llm = HuggingFaceHub(\n", + " repo_id=\"tiiuae/falcon-7b-instruct\",\n", + " model_kwargs={\"temperature\": 0.5, \"max_length\": 64},\n", + ")\n", + "\n", + "toolkit_hf = AmadeusToolkit(llm=llm)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -80,142 +141,142 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": { - "tags": [] + "tags": [], + "ExecuteTime": { + "end_time": "2024-01-13T17:46:00.148691365Z", + "start_time": "2024-01-13T17:45:59.317173243Z" + } }, "outputs": [], "source": [ - "from langchain.agents import AgentType, initialize_agent\n", - "from langchain_openai import OpenAI" + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_react_agent\n", + "from langchain_openai import ChatOpenAI" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": { - "tags": [] + "tags": [], + "ExecuteTime": { + "end_time": "2024-01-13T17:46:01.270044101Z", + "start_time": "2024-01-13T17:46:00.148988945Z" + } }, "outputs": [], "source": [ - "llm = OpenAI(temperature=0)\n", - "agent = initialize_agent(\n", + "llm = ChatOpenAI(temperature=0)\n", + "\n", + "prompt = hub.pull(\"hwchase17/react\")\n", + "agent = create_react_agent(llm, tools, prompt)\n", + "\n", + "agent_executor = AgentExecutor(\n", + " agent=agent,\n", " tools=tools,\n", - " llm=llm,\n", - " verbose=False,\n", - " agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,\n", + " verbose=True,\n", ")" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": { - "tags": [] + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-01-13T17:46:06.176227412Z", + "start_time": "2024-01-13T17:46:01.272468682Z" + } }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001B[1m> Entering new AgentExecutor chain...\u001B[0m\n", + "\u001B[32;1m\u001B[1;3mI should use the closest_airport tool to find the airport in Cali, Colombia.\n", + "Action: closest_airport\n", + "Action Input: location= \"Cali, Colombia\"\u001B[0m\u001B[36;1m\u001B[1;3mcontent='{\\n \"iataCode\": \"CLO\"\\n}'\u001B[0m\u001B[32;1m\u001B[1;3mThe airport in Cali, Colombia is called CLO.\n", + "Final Answer: CLO\u001B[0m\n", + "\n", + "\u001B[1m> Finished chain.\u001B[0m\n" + ] + }, { "data": { - "text/plain": [ - "'The closest airport to Cali, Colombia is Alfonso Bonilla Aragón International Airport (CLO).'" - ] + "text/plain": "{'input': 'What is the name of the airport in Cali, Colombia?',\n 'output': 'CLO'}" }, - "execution_count": 6, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "agent.run(\"What is the name of the airport in Cali, Colombia?\")" + "agent_executor.invoke({\"input\": \"What is the name of the airport in Cali, Colombia?\"})" ] }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [] + }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "'The cheapest flight on August 23, 2023 leaving Dallas, Texas before noon to Lincoln, Nebraska has a departure time of 16:42 and a total price of 276.08 EURO.'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "agent.run(\n", - " \"What is the departure time of the cheapest flight on August 23, 2023 leaving Dallas, Texas before noon to Lincoln, Nebraska?\"\n", + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"What is the departure time of the cheapest flight on August 23, 2023 leaving Dallas, Texas before noon to Lincoln, Nebraska?\"\n", + " }\n", ")" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'The earliest flight on August 23, 2023 leaving Dallas, Texas to Lincoln, Nebraska lands in Lincoln, Nebraska at 16:07.'" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "agent.run(\n", - " \"At what time does earliest flight on August 23, 2023 leaving Dallas, Texas to Lincoln, Nebraska land in Nebraska?\"\n", + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"At what time does earliest flight on August 23, 2023 leaving Dallas, Texas to Lincoln, Nebraska land in Nebraska?\"\n", + " }\n", ")" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'The cheapest flight between Portland, Oregon to Dallas, TX on October 3, 2023 is a Spirit Airlines flight with a total price of 84.02 EURO and a total travel time of 8 hours and 43 minutes.'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "agent.run(\n", - " \"What is the full travel time for the cheapest flight between Portland, Oregon to Dallas, TX on October 3, 2023?\"\n", + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"What is the full travel time for the cheapest flight between Portland, Oregon to Dallas, TX on October 3, 2023?\"\n", + " }\n", ")" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Dear Paul,\\n\\nI am writing to request that you book the earliest flight from DFW to DCA on Aug 28, 2023. The flight details are as follows:\\n\\nFlight 1: DFW to ATL, departing at 7:15 AM, arriving at 10:25 AM, flight number 983, carrier Delta Air Lines\\nFlight 2: ATL to DCA, departing at 12:15 PM, arriving at 2:02 PM, flight number 759, carrier Delta Air Lines\\n\\nThank you for your help.\\n\\nSincerely,\\nSantiago'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "agent.run(\n", - " \"Please draft a concise email from Santiago to Paul, Santiago's travel agent, asking him to book the earliest flight from DFW to DCA on Aug 28, 2023. Include all flight details in the email.\"\n", + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"Please draft a concise email from Santiago to Paul, Santiago's travel agent, asking him to book the earliest flight from DFW to DCA on Aug 28, 2023. Include all flight details in the email.\"\n", + " }\n", ")" ] } diff --git a/libs/community/langchain_community/tools/amadeus/closest_airport.py b/libs/community/langchain_community/tools/amadeus/closest_airport.py index 4107cc500ba30..cf108f3b11d99 100644 --- a/libs/community/langchain_community/tools/amadeus/closest_airport.py +++ b/libs/community/langchain_community/tools/amadeus/closest_airport.py @@ -58,4 +58,4 @@ def _run( ' Location Identifier" ' ) - return self.llm.predict(content) + return self.llm.invoke(content) From 6137c7608d80ee527b486655bfa1d3e6134254b6 Mon Sep 17 00:00:00 2001 From: Bhadresh Savani <bhadreshpsavani@gmail.com> Date: Tue, 16 Jan 2024 00:23:22 +0530 Subject: [PATCH 012/215] docs: Integration Documentation updated run to invoke for llms/ai21.ipynb (#15889) - **Description:** Updated Integration Documentation for [llms/ai21.ipynb](https://github.com/langchain-ai/langchain/blob/master/docs/docs/integrations/llms/ai21.ipynb) - **Issue:** #15664, - **Dependencies:** NA, - **Twitter handle:** @BhadreshSavani --- docs/docs/integrations/llms/ai21.ipynb | 52 +++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/docs/integrations/llms/ai21.ipynb b/docs/docs/integrations/llms/ai21.ipynb index 3adbd7098745a..2e22f85f11d59 100644 --- a/docs/docs/integrations/llms/ai21.ipynb +++ b/docs/docs/integrations/llms/ai21.ipynb @@ -14,12 +14,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "02be122d-04e8-4ec6-84d1-f1d8961d6828", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING: There was an error checking the latest version of pip.\u001b[0m\u001b[33m\n", + "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ "# install the package:\n", "%pip install --upgrade --quiet ai21" @@ -27,20 +36,12 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "id": "4229227e-6ca2-41ad-a3c3-5f29e3559091", "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdin", - "output_type": "stream", - "text": [ - " ········\n" - ] - } - ], + "outputs": [], "source": [ "# get AI21_API_KEY. Use https://studio.ai21.com/account/account\n", "\n", @@ -51,21 +52,20 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "6fb585dd", "metadata": { "tags": [] }, "outputs": [], "source": [ - "from langchain.chains import LLMChain\n", - "from langchain.prompts import PromptTemplate\n", - "from langchain_community.llms import AI21" + "from langchain_community.llms import AI21\n", + "from langchain_core.prompts import PromptTemplate" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "id": "035dea0f", "metadata": { "tags": [] @@ -76,12 +76,12 @@ "\n", "Answer: Let's think step by step.\"\"\"\n", "\n", - "prompt = PromptTemplate(template=template, input_variables=[\"question\"])" + "prompt = PromptTemplate.from_template(template)" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "3f3458d9", "metadata": { "tags": [] @@ -93,19 +93,19 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "a641dbd9", "metadata": { "tags": [] }, "outputs": [], "source": [ - "llm_chain = LLMChain(prompt=prompt, llm=llm)" + "llm_chain = prompt | llm" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "9f0b1960", "metadata": { "tags": [] @@ -114,10 +114,10 @@ { "data": { "text/plain": [ - "'\\n1. What year was Justin Bieber born?\\nJustin Bieber was born in 1994.\\n2. What team won the Super Bowl in 1994?\\nThe Dallas Cowboys won the Super Bowl in 1994.'" + "'\\nThe Super Bowl in the year Justin Beiber was born was in the year 1991.\\nThe Super Bowl in 1991 was won by the Washington Redskins.\\nFinal answer: Washington Redskins'" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -125,7 +125,7 @@ "source": [ "question = \"What NFL team won the Super Bowl in the year Justin Beiber was born?\"\n", "\n", - "llm_chain.run(question)" + "llm_chain.invoke({\"question\": question})" ] }, { @@ -153,7 +153,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" + "version": "3.10.13" } }, "nbformat": 4, From fb676d8a9bbbceb23f78ce544263361a347af1c8 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Mon, 15 Jan 2024 10:54:49 -0800 Subject: [PATCH 013/215] community[minor], langchain[minor]: refactor `output_parsers` Rail (#15852) Moved Rail parser to `community` package. --- .../output_parsers/__init__.py | 14 +++ .../output_parsers/rail_parser.py | 109 +++++++++++++++++ .../langchain/output_parsers/rail_parser.py | 112 +----------------- 3 files changed, 127 insertions(+), 108 deletions(-) create mode 100644 libs/community/langchain_community/output_parsers/__init__.py create mode 100644 libs/community/langchain_community/output_parsers/rail_parser.py diff --git a/libs/community/langchain_community/output_parsers/__init__.py b/libs/community/langchain_community/output_parsers/__init__.py new file mode 100644 index 0000000000000..62740af544177 --- /dev/null +++ b/libs/community/langchain_community/output_parsers/__init__.py @@ -0,0 +1,14 @@ +"""**OutputParser** classes parse the output of an LLM call. + +**Class hierarchy:** + +.. code-block:: + + BaseLLMOutputParser --> BaseOutputParser --> <name>OutputParser # GuardrailsOutputParser + +**Main helpers:** + +.. code-block:: + + Serializable, Generation, PromptValue +""" # noqa: E501 diff --git a/libs/community/langchain_community/output_parsers/rail_parser.py b/libs/community/langchain_community/output_parsers/rail_parser.py new file mode 100644 index 0000000000000..f0cabc13eb553 --- /dev/null +++ b/libs/community/langchain_community/output_parsers/rail_parser.py @@ -0,0 +1,109 @@ +from __future__ import annotations + +from typing import Any, Callable, Dict, Optional + +from langchain_core.output_parsers import BaseOutputParser + + +class GuardrailsOutputParser(BaseOutputParser): + """Parse the output of an LLM call using Guardrails.""" + + guard: Any + """The Guardrails object.""" + api: Optional[Callable] + """The LLM API passed to Guardrails during parsing. An example is `openai.completions.create`.""" # noqa: E501 + args: Any + """Positional arguments to pass to the above LLM API callable.""" + kwargs: Any + """Keyword arguments to pass to the above LLM API callable.""" + + @property + def _type(self) -> str: + return "guardrails" + + @classmethod + def from_rail( + cls, + rail_file: str, + num_reasks: int = 1, + api: Optional[Callable] = None, + *args: Any, + **kwargs: Any, + ) -> GuardrailsOutputParser: + """Create a GuardrailsOutputParser from a rail file. + + Args: + rail_file: a rail file. + num_reasks: number of times to re-ask the question. + api: the API to use for the Guardrails object. + *args: The arguments to pass to the API + **kwargs: The keyword arguments to pass to the API. + + Returns: + GuardrailsOutputParser + """ + try: + from guardrails import Guard + except ImportError: + raise ImportError( + "guardrails-ai package not installed. " + "Install it by running `pip install guardrails-ai`." + ) + return cls( + guard=Guard.from_rail(rail_file, num_reasks=num_reasks), + api=api, + args=args, + kwargs=kwargs, + ) + + @classmethod + def from_rail_string( + cls, + rail_str: str, + num_reasks: int = 1, + api: Optional[Callable] = None, + *args: Any, + **kwargs: Any, + ) -> GuardrailsOutputParser: + try: + from guardrails import Guard + except ImportError: + raise ImportError( + "guardrails-ai package not installed. " + "Install it by running `pip install guardrails-ai`." + ) + return cls( + guard=Guard.from_rail_string(rail_str, num_reasks=num_reasks), + api=api, + args=args, + kwargs=kwargs, + ) + + @classmethod + def from_pydantic( + cls, + output_class: Any, + num_reasks: int = 1, + api: Optional[Callable] = None, + *args: Any, + **kwargs: Any, + ) -> GuardrailsOutputParser: + try: + from guardrails import Guard + except ImportError: + raise ImportError( + "guardrails-ai package not installed. " + "Install it by running `pip install guardrails-ai`." + ) + return cls( + guard=Guard.from_pydantic(output_class, "", num_reasks=num_reasks), + api=api, + args=args, + kwargs=kwargs, + ) + + def get_format_instructions(self) -> str: + return self.guard.raw_prompt.format_instructions + + def parse(self, text: str) -> Dict: + return self.guard.parse(text, llm_api=self.api, *self.args, **self.kwargs) diff --git a/libs/langchain/langchain/output_parsers/rail_parser.py b/libs/langchain/langchain/output_parsers/rail_parser.py index f0cabc13eb553..3f796938be162 100644 --- a/libs/langchain/langchain/output_parsers/rail_parser.py +++ b/libs/langchain/langchain/output_parsers/rail_parser.py @@ -1,109 +1,5 @@ -from __future__ import annotations +from langchain_community.output_parsers.rail_parser import ( + GuardrailsOutputParser, +) -from typing import Any, Callable, Dict, Optional - -from langchain_core.output_parsers import BaseOutputParser - - -class GuardrailsOutputParser(BaseOutputParser): - """Parse the output of an LLM call using Guardrails.""" - - guard: Any - """The Guardrails object.""" - api: Optional[Callable] - """The LLM API passed to Guardrails during parsing. An example is `openai.completions.create`.""" # noqa: E501 - args: Any - """Positional arguments to pass to the above LLM API callable.""" - kwargs: Any - """Keyword arguments to pass to the above LLM API callable.""" - - @property - def _type(self) -> str: - return "guardrails" - - @classmethod - def from_rail( - cls, - rail_file: str, - num_reasks: int = 1, - api: Optional[Callable] = None, - *args: Any, - **kwargs: Any, - ) -> GuardrailsOutputParser: - """Create a GuardrailsOutputParser from a rail file. - - Args: - rail_file: a rail file. - num_reasks: number of times to re-ask the question. - api: the API to use for the Guardrails object. - *args: The arguments to pass to the API - **kwargs: The keyword arguments to pass to the API. - - Returns: - GuardrailsOutputParser - """ - try: - from guardrails import Guard - except ImportError: - raise ImportError( - "guardrails-ai package not installed. " - "Install it by running `pip install guardrails-ai`." - ) - return cls( - guard=Guard.from_rail(rail_file, num_reasks=num_reasks), - api=api, - args=args, - kwargs=kwargs, - ) - - @classmethod - def from_rail_string( - cls, - rail_str: str, - num_reasks: int = 1, - api: Optional[Callable] = None, - *args: Any, - **kwargs: Any, - ) -> GuardrailsOutputParser: - try: - from guardrails import Guard - except ImportError: - raise ImportError( - "guardrails-ai package not installed. " - "Install it by running `pip install guardrails-ai`." - ) - return cls( - guard=Guard.from_rail_string(rail_str, num_reasks=num_reasks), - api=api, - args=args, - kwargs=kwargs, - ) - - @classmethod - def from_pydantic( - cls, - output_class: Any, - num_reasks: int = 1, - api: Optional[Callable] = None, - *args: Any, - **kwargs: Any, - ) -> GuardrailsOutputParser: - try: - from guardrails import Guard - except ImportError: - raise ImportError( - "guardrails-ai package not installed. " - "Install it by running `pip install guardrails-ai`." - ) - return cls( - guard=Guard.from_pydantic(output_class, "", num_reasks=num_reasks), - api=api, - args=args, - kwargs=kwargs, - ) - - def get_format_instructions(self) -> str: - return self.guard.raw_prompt.format_instructions - - def parse(self, text: str) -> Dict: - return self.guard.parse(text, llm_api=self.api, *self.args, **self.kwargs) +__all__ = ["GuardrailsOutputParser"] From 15c2b4a47ef648e3820c54301090f2ab5624d17b Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Mon, 15 Jan 2024 20:04:11 +0100 Subject: [PATCH 014/215] community[minor]: Add AstraDB self query retriever (#15738) - **Description:** this change adds a self-query retriever for AstraDB - **Twitter handle:** cbornet_ --- .../retrievers/self_query/astradb.py | 70 ++++++++++ .../langchain/retrievers/self_query/base.py | 3 + .../retrievers/self_query/test_astradb.py | 131 ++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 libs/langchain/langchain/retrievers/self_query/astradb.py create mode 100644 libs/langchain/tests/unit_tests/retrievers/self_query/test_astradb.py diff --git a/libs/langchain/langchain/retrievers/self_query/astradb.py b/libs/langchain/langchain/retrievers/self_query/astradb.py new file mode 100644 index 0000000000000..0b8d3ab800f7c --- /dev/null +++ b/libs/langchain/langchain/retrievers/self_query/astradb.py @@ -0,0 +1,70 @@ +"""Logic for converting internal query language to a valid AstraDB query.""" +from typing import Dict, Tuple, Union + +from langchain.chains.query_constructor.ir import ( + Comparator, + Comparison, + Operation, + Operator, + StructuredQuery, + Visitor, +) + +MULTIPLE_ARITY_COMPARATORS = [Comparator.IN, Comparator.NIN] + + +class AstraDBTranslator(Visitor): + """Translate AstraDB internal query language elements to valid filters.""" + + """Subset of allowed logical comparators.""" + allowed_comparators = [ + Comparator.EQ, + Comparator.NE, + Comparator.GT, + Comparator.GTE, + Comparator.LT, + Comparator.LTE, + Comparator.IN, + Comparator.NIN, + ] + + """Subset of allowed logical operators.""" + allowed_operators = [Operator.AND, Operator.OR] + + def _format_func(self, func: Union[Operator, Comparator]) -> str: + self._validate_func(func) + map_dict = { + Operator.AND: "$and", + Operator.OR: "$or", + Comparator.EQ: "$eq", + Comparator.NE: "$ne", + Comparator.GTE: "$gte", + Comparator.LTE: "$lte", + Comparator.LT: "$lt", + Comparator.GT: "$gt", + Comparator.IN: "$in", + Comparator.NIN: "$nin", + } + return map_dict[func] + + def visit_operation(self, operation: Operation) -> Dict: + args = [arg.accept(self) for arg in operation.arguments] + return {self._format_func(operation.operator): args} + + def visit_comparison(self, comparison: Comparison) -> Dict: + if comparison.comparator in MULTIPLE_ARITY_COMPARATORS and not isinstance( + comparison.value, list + ): + comparison.value = [comparison.value] + + comparator = self._format_func(comparison.comparator) + return {comparison.attribute: {comparator: comparison.value}} + + def visit_structured_query( + self, structured_query: StructuredQuery + ) -> Tuple[str, dict]: + if structured_query.filter is None: + kwargs = {} + else: + kwargs = {"filter": structured_query.filter.accept(self)} + return structured_query.query, kwargs diff --git a/libs/langchain/langchain/retrievers/self_query/base.py b/libs/langchain/langchain/retrievers/self_query/base.py index 4a033580b4fff..9c6a584a6e174 100644 --- a/libs/langchain/langchain/retrievers/self_query/base.py +++ b/libs/langchain/langchain/retrievers/self_query/base.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union from langchain_community.vectorstores import ( + AstraDB, Chroma, DashVector, DeepLake, @@ -33,6 +34,7 @@ from langchain.chains.query_constructor.base import load_query_constructor_runnable from langchain.chains.query_constructor.ir import StructuredQuery, Visitor from langchain.chains.query_constructor.schema import AttributeInfo +from langchain.retrievers.self_query.astradb import AstraDBTranslator from langchain.retrievers.self_query.chroma import ChromaTranslator from langchain.retrievers.self_query.dashvector import DashvectorTranslator from langchain.retrievers.self_query.deeplake import DeepLakeTranslator @@ -55,6 +57,7 @@ def _get_builtin_translator(vectorstore: VectorStore) -> Visitor: """Get the translator class corresponding to the vector store class.""" BUILTIN_TRANSLATORS: Dict[Type[VectorStore], Type[Visitor]] = { + AstraDB: AstraDBTranslator, Pinecone: PineconeTranslator, Chroma: ChromaTranslator, DashVector: DashvectorTranslator, diff --git a/libs/langchain/tests/unit_tests/retrievers/self_query/test_astradb.py b/libs/langchain/tests/unit_tests/retrievers/self_query/test_astradb.py new file mode 100644 index 0000000000000..78fc47603a434 --- /dev/null +++ b/libs/langchain/tests/unit_tests/retrievers/self_query/test_astradb.py @@ -0,0 +1,131 @@ +from typing import Dict, Tuple + +from langchain.chains.query_constructor.ir import ( + Comparator, + Comparison, + Operation, + Operator, + StructuredQuery, +) +from langchain.retrievers.self_query.astradb import AstraDBTranslator + +DEFAULT_TRANSLATOR = AstraDBTranslator() + + +def test_visit_comparison_lt() -> None: + comp = Comparison(comparator=Comparator.LT, attribute="qty", value=20) + expected = {"qty": {"$lt": 20}} + actual = DEFAULT_TRANSLATOR.visit_comparison(comp) + assert expected == actual + + +def test_visit_comparison_eq() -> None: + comp = Comparison(comparator=Comparator.EQ, attribute="qty", value=10) + expected = {"qty": {"$eq": 10}} + actual = DEFAULT_TRANSLATOR.visit_comparison(comp) + assert expected == actual + + +def test_visit_comparison_ne() -> None: + comp = Comparison(comparator=Comparator.NE, attribute="name", value="foo") + expected = {"name": {"$ne": "foo"}} + actual = DEFAULT_TRANSLATOR.visit_comparison(comp) + assert expected == actual + + +def test_visit_comparison_in() -> None: + comp = Comparison(comparator=Comparator.IN, attribute="name", value="foo") + expected = {"name": {"$in": ["foo"]}} + actual = DEFAULT_TRANSLATOR.visit_comparison(comp) + assert expected == actual + + +def test_visit_comparison_nin() -> None: + comp = Comparison(comparator=Comparator.NIN, attribute="name", value="foo") + expected = {"name": {"$nin": ["foo"]}} + actual = DEFAULT_TRANSLATOR.visit_comparison(comp) + assert expected == actual + + +def test_visit_operation() -> None: + op = Operation( + operator=Operator.AND, + arguments=[ + Comparison(comparator=Comparator.GTE, attribute="qty", value=10), + Comparison(comparator=Comparator.LTE, attribute="qty", value=20), + Comparison(comparator=Comparator.EQ, attribute="name", value="foo"), + ], + ) + expected = { + "$and": [ + {"qty": {"$gte": 10}}, + {"qty": {"$lte": 20}}, + {"name": {"$eq": "foo"}}, + ] + } + actual = DEFAULT_TRANSLATOR.visit_operation(op) + assert expected == actual + + +def test_visit_structured_query_no_filter() -> None: + query = "What is the capital of France?" + structured_query = StructuredQuery( + query=query, + filter=None, + ) + expected: Tuple[str, Dict] = (query, {}) + actual = DEFAULT_TRANSLATOR.visit_structured_query(structured_query) + assert expected == actual + + +def test_visit_structured_query_one_attr() -> None: + query = "What is the capital of France?" + comp = Comparison(comparator=Comparator.IN, attribute="qty", value=[5, 15, 20]) + structured_query = StructuredQuery( + query=query, + filter=comp, + ) + expected = ( + query, + {"filter": {"qty": {"$in": [5, 15, 20]}}}, + ) + actual = DEFAULT_TRANSLATOR.visit_structured_query(structured_query) + assert expected == actual + + +def test_visit_structured_query_deep_nesting() -> None: + query = "What is the capital of France?" + op = Operation( + operator=Operator.AND, + arguments=[ + Comparison(comparator=Comparator.EQ, attribute="name", value="foo"), + Operation( + operator=Operator.OR, + arguments=[ + Comparison(comparator=Comparator.GT, attribute="qty", value=6), + Comparison( + comparator=Comparator.NIN, + attribute="tags", + value=["bar", "foo"], + ), + ], + ), + ], + ) + structured_query = StructuredQuery( + query=query, + filter=op, + ) + expected = ( + query, + { + "filter": { + "$and": [ + {"name": {"$eq": "foo"}}, + {"$or": [{"qty": {"$gt": 6}}, {"tags": {"$nin": ["bar", "foo"]}}]}, + ] + } + }, + ) + actual = DEFAULT_TRANSLATOR.visit_structured_query(structured_query) + assert expected == actual From 21e0df937f57e14e23d2f605019be01658fe7b3a Mon Sep 17 00:00:00 2001 From: Neo Zhao <zxzhao@ustc.edu> Date: Tue, 16 Jan 2024 03:13:14 +0800 Subject: [PATCH 015/215] community[patch]: fix a bug that mistakenly handle zip iterator in FAISS.from_embeddings (#16020) **Description**: `zip` is iterator that will only produce result once, so the previous code will cause the `embeddings` to be an empty list. **Issue**: I could not find a related issue. **Dependencies**: this PR does not introduce or affect dependencies. --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- libs/community/langchain_community/vectorstores/faiss.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/faiss.py b/libs/community/langchain_community/vectorstores/faiss.py index 4e7e5619e192c..8ca609b72592b 100644 --- a/libs/community/langchain_community/vectorstores/faiss.py +++ b/libs/community/langchain_community/vectorstores/faiss.py @@ -986,11 +986,10 @@ def from_embeddings( text_embedding_pairs = zip(texts, text_embeddings) faiss = FAISS.from_embeddings(text_embedding_pairs, embeddings) """ - texts = [t[0] for t in text_embeddings] - embeddings = [t[1] for t in text_embeddings] + texts, embeddings = zip(*text_embeddings) return cls.__from( - texts, - embeddings, + list(texts), + list(embeddings), embedding, metadatas=metadatas, ids=ids, From 7306032dcffe1f450c72222b07277b7b8f1e44b4 Mon Sep 17 00:00:00 2001 From: Bigtable123 <63093159+Bigtable123@users.noreply.github.com> Date: Tue, 16 Jan 2024 03:13:21 +0800 Subject: [PATCH 016/215] docs: update baidu_qianfan_endpoint.ipynb doc (#15940) - **Description:** Updated the docs for the chat integration module baidu_qianfan_endpoint.ipynb - **Issue:** #15664 - **Dependencies:**N/A --- .../chat/baidu_qianfan_endpoint.ipynb | 224 ++++++++++-------- 1 file changed, 120 insertions(+), 104 deletions(-) diff --git a/docs/docs/integrations/chat/baidu_qianfan_endpoint.ipynb b/docs/docs/integrations/chat/baidu_qianfan_endpoint.ipynb index 43a1336c1b99a..34b524d8dee79 100644 --- a/docs/docs/integrations/chat/baidu_qianfan_endpoint.ipynb +++ b/docs/docs/integrations/chat/baidu_qianfan_endpoint.ipynb @@ -53,9 +53,16 @@ "- AquilaChat-7B" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -65,83 +72,105 @@ "from langchain_community.chat_models import QianfanChatEndpoint\n", "from langchain_core.language_models.chat_models import HumanMessage\n", "\n", - "os.environ[\"QIANFAN_AK\"] = \"your_ak\"\n", - "os.environ[\"QIANFAN_SK\"] = \"your_sk\"\n", - "\n", - "chat = QianfanChatEndpoint(\n", - " streaming=True,\n", - ")\n", - "res = chat([HumanMessage(content=\"write a funny joke\")])" + "os.environ[\"QIANFAN_AK\"] = \"Your_api_key\"\n", + "os.environ[\"QIANFAN_SK\"] = \"You_secret_Key\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usage" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "[INFO] [09-15 20:00:36] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/eb-instant\n", - "[INFO] [09-15 20:00:37] logging.py:55 [t:139698882193216]: async requesting llm api endpoint: /chat/eb-instant\n" - ] - }, + "data": { + "text/plain": [ + "AIMessage(content='您好!请问您需要什么帮助?我将尽力回答您的问题。')" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chat = QianfanChatEndpoint(streaming=True)\n", + "messages = [HumanMessage(content=\"Hello\")]\n", + "chat.invoke(messages)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "chat resp: content='您好,您似乎输入' additional_kwargs={} example=False\n", - "chat resp: content='了一个话题标签,请问需要我帮您找到什么资料或者帮助您解答什么问题吗?' additional_kwargs={} example=False\n", - "chat resp: content='' additional_kwargs={} example=False\n" - ] - }, + "data": { + "text/plain": [ + "AIMessage(content='您好!有什么我可以帮助您的吗?')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await chat.ainvoke(messages)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "[INFO] [09-15 20:00:39] logging.py:55 [t:139698882193216]: async requesting llm api endpoint: /chat/eb-instant\n" - ] - }, + "data": { + "text/plain": [ + "[AIMessage(content='您好!有什么我可以帮助您的吗?')]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chat.batch([messages])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Streaming" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "generations=[[ChatGeneration(text=\"The sea is a vast expanse of water that covers much of the Earth's surface. It is a source of travel, trade, and entertainment, and is also a place of scientific exploration and marine conservation. The sea is an important part of our world, and we should cherish and protect it.\", generation_info={'finish_reason': 'finished'}, message=AIMessage(content=\"The sea is a vast expanse of water that covers much of the Earth's surface. It is a source of travel, trade, and entertainment, and is also a place of scientific exploration and marine conservation. The sea is an important part of our world, and we should cherish and protect it.\", additional_kwargs={}, example=False))]] llm_output={} run=[RunInfo(run_id=UUID('d48160a6-5960-4c1d-8a0e-90e6b51a209b'))]\n", - "astream content='The sea is a vast' additional_kwargs={} example=False\n", - "astream content=' expanse of water, a place of mystery and adventure. It is the source of many cultures and civilizations, and a center of trade and exploration. The sea is also a source of life and beauty, with its unique marine life and diverse' additional_kwargs={} example=False\n", - "astream content=' coral reefs. Whether you are swimming, diving, or just watching the sea, it is a place that captivates the imagination and transforms the spirit.' additional_kwargs={} example=False\n" + "您好!有什么我可以帮助您的吗?\n" ] } ], "source": [ - "from langchain.schema import HumanMessage\n", - "from langchain_community.chat_models import QianfanChatEndpoint\n", - "\n", - "chatLLM = QianfanChatEndpoint()\n", - "res = chatLLM.stream([HumanMessage(content=\"hi\")], streaming=True)\n", - "for r in res:\n", - " print(\"chat resp:\", r)\n", - "\n", - "\n", - "async def run_aio_generate():\n", - " resp = await chatLLM.agenerate(\n", - " messages=[[HumanMessage(content=\"write a 20 words sentence about sea.\")]]\n", - " )\n", - " print(resp)\n", - "\n", - "\n", - "await run_aio_generate()\n", - "\n", - "\n", - "async def run_aio_stream():\n", - " async for res in chatLLM.astream(\n", - " [HumanMessage(content=\"write a 20 words sentence about sea.\")]\n", - " ):\n", - " print(\"astream\", res)\n", - "\n", - "\n", - "await run_aio_stream()" + "try:\n", + " for chunk in chat.stream(messages):\n", + " print(chunk.content, end=\"\", flush=True)\n", + "except TypeError as e:\n", + " print(\"\")" ] }, { @@ -151,39 +180,36 @@ "source": [ "## Use different models in Qianfan\n", "\n", - "In the case you want to deploy your own model based on Ernie Bot or third-party open-source model, you could follow these steps:\n", + "The default model is ERNIE-Bot-turbo, in the case you want to deploy your own model based on Ernie Bot or third-party open-source model, you could follow these steps:\n", "\n", - "- 1. (Optional, if the model are included in the default models, skip it)Deploy your model in Qianfan Console, get your own customized deploy endpoint.\n", - "- 2. Set up the field called `endpoint` in the initialization:" + "1. (Optional, if the model are included in the default models, skip it) Deploy your model in Qianfan Console, get your own customized deploy endpoint.\n", + "2. Set up the field called `endpoint` in the initialization:" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "[INFO] [09-15 20:00:50] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/bloomz_7b1\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "content='你好!很高兴见到你。' additional_kwargs={} example=False\n" - ] + "data": { + "text/plain": [ + "AIMessage(content='Hello,可以回答问题了,我会竭尽全力为您解答,请问有什么问题吗?')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "chatBloom = QianfanChatEndpoint(\n", + "chatBot = QianfanChatEndpoint(\n", " streaming=True,\n", - " model=\"BLOOMZ-7B\",\n", + " model=\"ERNIE-Bot\",\n", ")\n", - "res = chatBloom([HumanMessage(content=\"hi\")])\n", - "print(res)" + "\n", + "messages = [HumanMessage(content=\"Hello\")]\n", + "chatBot.invoke(messages)" ] }, { @@ -202,35 +228,25 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "[INFO] [09-15 20:00:57] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/eb-instant\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "content='您好,您似乎输入' additional_kwargs={} example=False\n", - "content='了一个文本字符串,但并没有给出具体的问题或场景。' additional_kwargs={} example=False\n", - "content='如果您能提供更多信息,我可以更好地回答您的问题。' additional_kwargs={} example=False\n", - "content='' additional_kwargs={} example=False\n" - ] + "data": { + "text/plain": [ + "AIMessage(content='您好!有什么我可以帮助您的吗?')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "res = chat.stream(\n", - " [HumanMessage(content=\"hi\")],\n", + "chat.invoke(\n", + " [HumanMessage(content=\"Hello\")],\n", " **{\"top_p\": 0.4, \"temperature\": 0.1, \"penalty_score\": 1},\n", - ")\n", - "\n", - "for r in res:\n", - " print(r)" + ")" ] } ], @@ -250,7 +266,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.9.18" }, "vscode": { "interpreter": { From b11fd3bedc078fdc4990a3f34aac83d301f6119e Mon Sep 17 00:00:00 2001 From: JaguarDB <115371133+fserv@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:13:45 -0800 Subject: [PATCH 017/215] community[patch]: jaguar vector store fix integer-element error when joining metadata values (#15939) - **Description:** some document loaders add integer-type metadata values which cause error - **Issue:** 15937 - **Dependencies:** none --------- Co-authored-by: JY <jyjy@jaguardb> --- .../langchain_community/vectorstores/jaguar.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/vectorstores/jaguar.py b/libs/community/langchain_community/vectorstores/jaguar.py index 688cc81fa10bf..2771530d66dfb 100644 --- a/libs/community/langchain_community/vectorstores/jaguar.py +++ b/libs/community/langchain_community/vectorstores/jaguar.py @@ -158,6 +158,7 @@ def add_texts( """ vcol = self._vector_index filecol = kwargs.get("file_column", "") + text_tag = kwargs.get("text_tag", "") podstorevcol = self._pod + "." + self._store + "." + vcol q = "textcol " + podstorevcol js = self.run(q) @@ -165,6 +166,12 @@ def add_texts( return [] textcol = js["data"] + if text_tag != "": + tag_texts = [] + for t in texts: + tag_texts.append(text_tag + " " + t) + texts = tag_texts + embeddings = self._embedding.embed_documents(list(texts)) ids = [] if metadatas is None: @@ -444,4 +451,5 @@ def _parseMeta(self, nvmap: dict, filecol: str) -> Tuple[List[str], List[str], s nvec.append(k) vvec.append(v) - return nvec, vvec, filepath + vvec_s = [str(e) for e in vvec] + return nvec, vvec_s, filepath From beec7259c86593efe01d22f3680c043dc687c57a Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Mon, 15 Jan 2024 14:14:11 -0500 Subject: [PATCH 018/215] docs: Add info admonitions to a few agents (#15899) Add admonitions directly in the agent page to explain constraints and include a link to agent types. --- .../modules/agents/agent_types/structured_chat.ipynb | 4 ++-- docs/docs/modules/agents/agent_types/xml_agent.ipynb | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/docs/modules/agents/agent_types/structured_chat.ipynb b/docs/docs/modules/agents/agent_types/structured_chat.ipynb index 2ed2a9586eb7e..d2da33a6ba9bf 100644 --- a/docs/docs/modules/agents/agent_types/structured_chat.ipynb +++ b/docs/docs/modules/agents/agent_types/structured_chat.ipynb @@ -17,7 +17,7 @@ "source": [ "# Structured chat\n", "\n", - "The structured chat agent is capable of using multi-input tools.\n" + "The structured chat agent is capable of using multi-input tools." ] }, { @@ -237,7 +237,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/docs/docs/modules/agents/agent_types/xml_agent.ipynb b/docs/docs/modules/agents/agent_types/xml_agent.ipynb index 1a8d09bd3e669..5ba476a299085 100644 --- a/docs/docs/modules/agents/agent_types/xml_agent.ipynb +++ b/docs/docs/modules/agents/agent_types/xml_agent.ipynb @@ -17,7 +17,14 @@ "source": [ "# XML Agent\n", "\n", - "Some language models (like Anthropic's Claude) are particularly good at reasoning/writing XML. This goes over how to use an agent that uses XML when prompting. " + "Some language models (like Anthropic's Claude) are particularly good at reasoning/writing XML. This goes over how to use an agent that uses XML when prompting. \n", + "\n", + ":::tip\n", + "\n", + "* Use with regular LLMs, not with chat models.\n", + "* Use only with unstructured tools; i.e., tools that accept a single string input.\n", + "* See [AgentTypes](../index) documentation for more agent types.\n", + ":::" ] }, { @@ -217,7 +224,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.11.4" } }, "nbformat": 4, From 7c57cfd8f04a03c9085a3b39b0c4c0c539b2d1f5 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Mon, 15 Jan 2024 14:14:29 -0500 Subject: [PATCH 019/215] docs: Update OpenAI functions agent (#15894) Add info and a tip explaining when to use this agent. --- .../agent_types/openai_functions_agent.ipynb | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/docs/modules/agents/agent_types/openai_functions_agent.ipynb b/docs/docs/modules/agents/agent_types/openai_functions_agent.ipynb index bcc2e8c996317..6aae9f78fa4f8 100644 --- a/docs/docs/modules/agents/agent_types/openai_functions_agent.ipynb +++ b/docs/docs/modules/agents/agent_types/openai_functions_agent.ipynb @@ -19,9 +19,27 @@ "\n", "Certain OpenAI models (like gpt-3.5-turbo-0613 and gpt-4-0613) have been fine-tuned to detect when a function should be called and respond with the inputs that should be passed to the function. In an API call, you can describe functions and have the model intelligently choose to output a JSON object containing arguments to call those functions. The goal of the OpenAI Function APIs is to more reliably return valid and useful function calls than a generic text completion or chat API.\n", "\n", + "A number of open source models have adopted the same format for function calls and have also fine-tuned the model to detect when a function should be called.\n", + "\n", "The OpenAI Functions Agent is designed to work with these models.\n", "\n", - "Install `openai`, `tavily-python` packages which are required as the LangChain packages call them internally." + "Install `openai`, `tavily-python` packages which are required as the LangChain packages call them internally.\n", + "\n", + "\n", + ":::info\n", + "\n", + "OpenAI API has deprecated `functions` in favor of `tools`. The difference between the two is that the `tools` API allows the model to request that multiple functions be invoked at once, which can reduce response times in some architectures. It's recommended to use the tools agent for OpenAI models.\n", + "\n", + "See the following links for more information:\n", + "\n", + "[OpenAI chat create](https://platform.openai.com/docs/api-reference/chat/create)\n", + "\n", + "[OpenAI function calling](https://platform.openai.com/docs/guides/function-calling)\n", + ":::\n", + "\n", + ":::tip\n", + "The `functions` format remains relevant for open source models and providers that have adopted it, and this agent is expected to work for such models.\n", + ":::\n" ] }, { @@ -260,7 +278,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.11.4" } }, "nbformat": 4, From daa9ccae5215704e29cfea98a341d732987321b0 Mon Sep 17 00:00:00 2001 From: axiangcoding <axiangcoding@gmail.com> Date: Tue, 16 Jan 2024 03:14:44 +0800 Subject: [PATCH 020/215] community[patch]: deprecate ErnieBotChat and ErnieEmbeddings classes (#15862) - **Description:** add deprecated warning for ErnieBotChat and ErnieEmbeddings. - These two classes **lack maintenance** and do not use the sdk provided by qianfan, which means hard to implement some key feature like streaming. - The alternative `langchain_community.chat_models.QianfanChatEndpoint` and `langchain_community.embeddings.QianfanEmbeddingsEndpoint` can completely replace these two classes, only need to change configuration items. - **Issue:** None, - **Dependencies:** None, - **Twitter handle:** None --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- docs/docs/integrations/chat/ernie.ipynb | 52 +++++++++++++++---- .../integrations/text_embedding/ernie.ipynb | 45 ++++++++++++++++ .../langchain_community/chat_models/ernie.py | 5 ++ .../langchain_community/embeddings/ernie.py | 5 ++ 4 files changed, 97 insertions(+), 10 deletions(-) diff --git a/docs/docs/integrations/chat/ernie.ipynb b/docs/docs/integrations/chat/ernie.ipynb index 17c5fb650cc9e..fd467c22276e4 100644 --- a/docs/docs/integrations/chat/ernie.ipynb +++ b/docs/docs/integrations/chat/ernie.ipynb @@ -16,29 +16,58 @@ "# ErnieBotChat\n", "\n", "[ERNIE-Bot](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11) is a large language model developed by Baidu, covering a huge amount of Chinese data.\n", - "This notebook covers how to get started with ErnieBot chat models.\n", + "This notebook covers how to get started with ErnieBot chat models." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Deprecated Warning**\n", + "\n", + "We recommend users using `langchain_community.chat_models.ErnieBotChat` \n", + "to use `langchain_community.chat_models.QianfanChatEndpoint` instead.\n", + "\n", + "documentation for `QianfanChatEndpoint` is [here](./baidu_qianfan_endpoint).\n", + "\n", + "they are 4 why we recommend users to use `QianfanChatEndpoint`:\n", "\n", - "**Note:** We recommend users using this class to switch to [Baidu Qianfan](./baidu_qianfan_endpoint). they are 3 why we recommend users to use `QianfanChatEndpoint`:\n", "1. `QianfanChatEndpoint` support more LLM in the Qianfan platform.\n", "2. `QianfanChatEndpoint` support streaming mode.\n", "3. `QianfanChatEndpoint` support function calling usgage.\n", - "\n", + "4. `ErnieBotChat` is lack of maintenance and deprecated." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Some tips for migration:\n", + "\n", "- change `ernie_client_id` to `qianfan_ak`, also change `ernie_client_secret` to `qianfan_sk`.\n", - "- install `qianfan` package. \n", - " ```\n", - " pip install qianfan\n", - " ```" + "- install `qianfan` package. like `pip install qianfan`\n", + "- change `ErnieBotChat` to `QianfanChatEndpoint`." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "from langchain.schema import HumanMessage\n", - "from langchain_community.chat_models import ErnieBotChat" + "from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint\n", + "\n", + "chat = QianfanChatEndpoint(\n", + " qianfan_ak=\"your qianfan ak\",\n", + " qianfan_sk=\"your qianfan sk\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usage" ] }, { @@ -47,6 +76,9 @@ "metadata": {}, "outputs": [], "source": [ + "from langchain.schema import HumanMessage\n", + "from langchain_community.chat_models import ErnieBotChat\n", + "\n", "chat = ErnieBotChat(\n", " ernie_client_id=\"YOUR_CLIENT_ID\", ernie_client_secret=\"YOUR_CLIENT_SECRET\"\n", ")" diff --git a/docs/docs/integrations/text_embedding/ernie.ipynb b/docs/docs/integrations/text_embedding/ernie.ipynb index fb1d0d523ba3e..d04bc4b9602e4 100644 --- a/docs/docs/integrations/text_embedding/ernie.ipynb +++ b/docs/docs/integrations/text_embedding/ernie.ipynb @@ -10,6 +10,51 @@ "which converts text into a vector form represented by numerical values, and is used in text retrieval, information recommendation, knowledge mining and other scenarios." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Deprecated Warning**\n", + "\n", + "We recommend users using `langchain_community.embeddings.ErnieEmbeddings` \n", + "to use `langchain_community.embeddings.QianfanEmbeddingsEndpoint` instead.\n", + "\n", + "documentation for `QianfanEmbeddingsEndpoint` is [here](./baidu_qianfan_endpoint).\n", + "\n", + "they are 2 why we recommend users to use `QianfanEmbeddingsEndpoint`:\n", + "\n", + "1. `QianfanEmbeddingsEndpoint` support more embedding model in the Qianfan platform.\n", + "2. `ErnieEmbeddings` is lack of maintenance and deprecated." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some tips for migration:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.embeddings import QianfanEmbeddingsEndpoint\n", + "\n", + "embeddings = QianfanEmbeddingsEndpoint(\n", + " qianfan_ak=\"your qianfan ak\",\n", + " qianfan_sk=\"your qianfan sk\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usage" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/libs/community/langchain_community/chat_models/ernie.py b/libs/community/langchain_community/chat_models/ernie.py index 8d69669afc2d4..9954ef4272fc9 100644 --- a/libs/community/langchain_community/chat_models/ernie.py +++ b/libs/community/langchain_community/chat_models/ernie.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Mapping, Optional import requests +from langchain_core._api.deprecation import deprecated from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.messages import ( @@ -30,6 +31,10 @@ def _convert_message_to_dict(message: BaseMessage) -> dict: return message_dict +@deprecated( + since="0.0.13", + alternative="langchain_community.chat_models.QianfanChatEndpoint", +) class ErnieBotChat(BaseChatModel): """`ERNIE-Bot` large language model. diff --git a/libs/community/langchain_community/embeddings/ernie.py b/libs/community/langchain_community/embeddings/ernie.py index 5467e4c027814..b6bff742bd1e4 100644 --- a/libs/community/langchain_community/embeddings/ernie.py +++ b/libs/community/langchain_community/embeddings/ernie.py @@ -4,6 +4,7 @@ from typing import Dict, List, Optional import requests +from langchain_core._api.deprecation import deprecated from langchain_core.embeddings import Embeddings from langchain_core.pydantic_v1 import BaseModel, root_validator from langchain_core.runnables.config import run_in_executor @@ -12,6 +13,10 @@ logger = logging.getLogger(__name__) +@deprecated( + since="0.0.13", + alternative="langchain_community.embeddings.QianfanEmbeddingsEndpoint", +) class ErnieEmbeddings(BaseModel, Embeddings): """`Ernie Embeddings V1` embedding models.""" From 9e779ca84647519e22ba38ee1bdc1be0523e066d Mon Sep 17 00:00:00 2001 From: Mohammad Mohtashim <45242107+keenborder786@users.noreply.github.com> Date: Tue, 16 Jan 2024 00:23:55 +0500 Subject: [PATCH 021/215] community[patch]: Fixing the SlackGetChannel Tool Input Error (#15725) Fixed the issue mentioned in #15698 for SlackGetChannel Tool. @baskaryan. --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> Co-authored-by: Bagatur <baskaryan@gmail.com> --- .../langchain_community/tools/slack/get_channel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/community/langchain_community/tools/slack/get_channel.py b/libs/community/langchain_community/tools/slack/get_channel.py index 0ed5d817a20ec..a3dbe13939a19 100644 --- a/libs/community/langchain_community/tools/slack/get_channel.py +++ b/libs/community/langchain_community/tools/slack/get_channel.py @@ -1,6 +1,6 @@ import json import logging -from typing import Optional +from typing import Any, Optional from langchain_core.callbacks import CallbackManagerForToolRun @@ -11,11 +11,12 @@ class SlackGetChannel(SlackBaseTool): """Tool that gets Slack channel information.""" name: str = "get_channelid_name_dict" - description: str = "Use this tool to get channelid-name dict." + description: str = ( + "Use this tool to get channelid-name dict. There is no input to this tool" + ) def _run( - self, - run_manager: Optional[CallbackManagerForToolRun] = None, + self, *args: Any, run_manager: Optional[CallbackManagerForToolRun] = None ) -> str: try: logging.getLogger(__name__) From ce21392a21383527366f350f002c2b7c4ffb7119 Mon Sep 17 00:00:00 2001 From: YHW <yhwjjang1995@naver.com> Date: Tue, 16 Jan 2024 04:25:23 +0900 Subject: [PATCH 022/215] community: add a flag that determines whether to load the milvus collection (#15693) fix https://github.com/langchain-ai/langchain/issues/15694 --------- Co-authored-by: hyungwookyang <hyungwookyang@worksmobile.com> Co-authored-by: Bagatur <baskaryan@gmail.com> --- .../langchain_community/vectorstores/milvus.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/milvus.py b/libs/community/langchain_community/vectorstores/milvus.py index 4c626edce8126..ed751271e0fb7 100644 --- a/libs/community/langchain_community/vectorstores/milvus.py +++ b/libs/community/langchain_community/vectorstores/milvus.py @@ -446,9 +446,15 @@ def _load( timeout: Optional[float] = None, ) -> None: """Load the collection if available.""" - from pymilvus import Collection - - if isinstance(self.col, Collection) and self._get_index() is not None: + from pymilvus import Collection, utility + from pymilvus.client.types import LoadState + + if ( + isinstance(self.col, Collection) + and self._get_index() is not None + and utility.load_state(self.collection_name, using=self.alias) + == LoadState.NotLoad + ): self.col.load( partition_names=partition_names, replica_number=replica_number, From ddf4e7c633eb7833ad3278507b5e7cbba280f7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9B=90=E7=B2=92=20Yanli?= <mail@yanli.one> Date: Tue, 16 Jan 2024 03:41:59 +0800 Subject: [PATCH 023/215] community[minor]: Update pgvecto_rs to use its high level sdk (#15574) - **Description:** Update pgvecto_rs to use its high level sdk, - **Issue:** fix #15173 --- .../vectorstores/pgvecto_rs.ipynb | 59 +++++++-- .../vectorstores/pgvecto_rs.py | 123 ++++++++---------- 2 files changed, 102 insertions(+), 80 deletions(-) diff --git a/docs/docs/integrations/vectorstores/pgvecto_rs.ipynb b/docs/docs/integrations/vectorstores/pgvecto_rs.ipynb index e216d530fc4fb..e72aefe9ec8bf 100644 --- a/docs/docs/integrations/vectorstores/pgvecto_rs.ipynb +++ b/docs/docs/integrations/vectorstores/pgvecto_rs.ipynb @@ -6,7 +6,7 @@ "source": [ "# PGVecto.rs\n", "\n", - "This notebook shows how to use functionality related to the Postgres vector database ([pgvecto.rs](https://github.com/tensorchord/pgvecto.rs)). You need to install SQLAlchemy >= 2 manually." + "This notebook shows how to use functionality related to the Postgres vector database ([pgvecto.rs](https://github.com/tensorchord/pgvecto.rs))." ] }, { @@ -15,10 +15,7 @@ "metadata": {}, "outputs": [], "source": [ - "## Loading Environment Variables\n", - "from dotenv import load_dotenv\n", - "\n", - "load_dotenv()" + "%pip install \"pgvecto_rs[sdk]\"" ] }, { @@ -32,8 +29,8 @@ "from langchain.docstore.document import Document\n", "from langchain.text_splitter import CharacterTextSplitter\n", "from langchain_community.document_loaders import TextLoader\n", - "from langchain_community.vectorstores.pgvecto_rs import PGVecto_rs\n", - "from langchain_openai import OpenAIEmbeddings" + "from langchain_community.embeddings.fake import FakeEmbeddings\n", + "from langchain_community.vectorstores.pgvecto_rs import PGVecto_rs" ] }, { @@ -42,12 +39,12 @@ "metadata": {}, "outputs": [], "source": [ - "loader = TextLoader(\"../../../state_of_the_union.txt\")\n", + "loader = TextLoader(\"../../modules/state_of_the_union.txt\")\n", "documents = loader.load()\n", "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", "docs = text_splitter.split_documents(documents)\n", "\n", - "embeddings = OpenAIEmbeddings()" + "embeddings = FakeEmbeddings(size=3)" ] }, { @@ -176,7 +173,42 @@ "outputs": [], "source": [ "query = \"What did the president say about Ketanji Brown Jackson\"\n", - "docs: List[Document] = db1.similarity_search(query, k=4)" + "docs: List[Document] = db1.similarity_search(query, k=4)\n", + "for doc in docs:\n", + " print(doc.page_content)\n", + " print(\"======================\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Similarity Search with Filter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pgvecto_rs.sdk.filters import meta_contains\n", + "\n", + "query = \"What did the president say about Ketanji Brown Jackson\"\n", + "docs: List[Document] = db1.similarity_search(\n", + " query, k=4, filter=meta_contains({\"source\": \"../../modules/state_of_the_union.txt\"})\n", + ")\n", + "\n", + "for doc in docs:\n", + " print(doc.page_content)\n", + " print(\"======================\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or:" ] }, { @@ -185,6 +217,11 @@ "metadata": {}, "outputs": [], "source": [ + "query = \"What did the president say about Ketanji Brown Jackson\"\n", + "docs: List[Document] = db1.similarity_search(\n", + " query, k=4, filter={\"source\": \"../../modules/state_of_the_union.txt\"}\n", + ")\n", + "\n", "for doc in docs:\n", " print(doc.page_content)\n", " print(\"======================\")" @@ -207,7 +244,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/libs/community/langchain_community/vectorstores/pgvecto_rs.py b/libs/community/langchain_community/vectorstores/pgvecto_rs.py index 6c4a887a2d8d2..18d66c80a893a 100644 --- a/libs/community/langchain_community/vectorstores/pgvecto_rs.py +++ b/libs/community/langchain_community/vectorstores/pgvecto_rs.py @@ -1,32 +1,17 @@ from __future__ import annotations import uuid -from typing import Any, Iterable, List, Literal, Optional, Tuple, Type +from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple, Union -import numpy as np -import sqlalchemy from langchain_core.documents import Document from langchain_core.embeddings import Embeddings from langchain_core.vectorstores import VectorStore -from sqlalchemy import insert, select -from sqlalchemy.dialects import postgresql -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column -from sqlalchemy.orm.session import Session - - -class _ORMBase(DeclarativeBase): - __tablename__: str - id: Mapped[uuid.UUID] - text: Mapped[str] - meta: Mapped[dict] - embedding: Mapped[np.ndarray] class PGVecto_rs(VectorStore): """VectorStore backed by pgvecto_rs.""" - _engine: sqlalchemy.engine.Engine - _table: Type[_ORMBase] + _store = None _embedding: Embeddings def __init__( @@ -45,28 +30,22 @@ def __init__( db_url: Database URL. collection_name: Name of the collection. new_table: Whether to create a new table or connect to an existing one. - Defaults to False. + If true, the table will be dropped if exists, then recreated. + Defaults to False. """ try: - from pgvecto_rs.sqlalchemy import Vector + from pgvecto_rs.sdk import PGVectoRs except ImportError as e: raise ImportError( - "Unable to import pgvector_rs, please install with " - "`pip install pgvector_rs`." + "Unable to import pgvector_rs.sdk , please install with " + '`pip install "pgvector_rs[sdk]"`.' ) from e - - class _Table(_ORMBase): - __tablename__ = f"collection_{collection_name}" - id: Mapped[uuid.UUID] = mapped_column( - postgresql.UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 - ) - text: Mapped[str] = mapped_column(sqlalchemy.String) - meta: Mapped[dict] = mapped_column(postgresql.JSONB) - embedding: Mapped[np.ndarray] = mapped_column(Vector(dimension)) - - self._engine = sqlalchemy.create_engine(db_url) - self._table = _Table - self._table.__table__.create(self._engine, checkfirst=not new_table) # type: ignore + self._store = PGVectoRs( + db_url=db_url, + collection_name=collection_name, + dimension=dimension, + recreate=new_table, + ) self._embedding = embedding # ================ Create interface ================= @@ -90,7 +69,6 @@ def from_texts( dimension=dimension, db_url=db_url, collection_name=collection_name, - new_table=True, ) _self.add_texts(texts, metadatas, **kwargs) return _self @@ -148,19 +126,15 @@ def add_texts( List of ids of the added texts. """ + from pgvecto_rs.sdk import Record + embeddings = self._embedding.embed_documents(list(texts)) - with Session(self._engine) as _session: - results: List[str] = [] - for text, embedding, metadata in zip( - texts, embeddings, metadatas or [dict()] * len(list(texts)) - ): - t = insert(self._table).values( - text=text, meta=metadata, embedding=embedding - ) - id = _session.execute(t).inserted_primary_key[0] # type: ignore - results.append(str(id)) - _session.commit() - return results + records = [ + Record.from_text(text, embedding, meta) + for text, embedding, meta in zip(texts, embeddings, metadatas or []) + ] + self._store.insert(records) + return [str(record.id) for record in records] def add_documents(self, documents: List[Document], **kwargs: Any) -> List[str]: """Run more documents through the embeddings and add to the vectorstore. @@ -185,31 +159,41 @@ def similarity_search_with_score_by_vector( distance_func: Literal[ "sqrt_euclid", "neg_dot_prod", "ned_cos" ] = "sqrt_euclid", + filter: Union[None, Dict[str, Any], Any] = None, **kwargs: Any, ) -> List[Tuple[Document, float]]: """Return docs most similar to query vector, with its score.""" - with Session(self._engine) as _session: - real_distance_func = ( - self._table.embedding.squared_euclidean_distance - if distance_func == "sqrt_euclid" - else self._table.embedding.negative_dot_product_distance - if distance_func == "neg_dot_prod" - else self._table.embedding.negative_cosine_distance - if distance_func == "ned_cos" - else None - ) - if real_distance_func is None: - raise ValueError("Invalid distance function") - t = ( - select(self._table, real_distance_func(query_vector).label("score")) - .order_by("score") - .limit(k) # type: ignore + from pgvecto_rs.sdk.filters import meta_contains + + distance_func_map = { + "sqrt_euclid": "<->", + "neg_dot_prod": "<#>", + "ned_cos": "<=>", + } + if filter is None: + real_filter = None + elif isinstance(filter, dict): + real_filter = meta_contains(filter) + else: + real_filter = filter + results = self._store.search( + query_vector, + distance_func_map[distance_func], + k, + filter=real_filter, + ) + + return [ + ( + Document( + page_content=res[0].text, + metadata=res[0].meta, + ), + res[1], ) - return [ - (Document(page_content=row[0].text, metadata=row[0].meta), row[1]) - for row in _session.execute(t) - ] + for res in results + ] def similarity_search_by_vector( self, @@ -218,11 +202,12 @@ def similarity_search_by_vector( distance_func: Literal[ "sqrt_euclid", "neg_dot_prod", "ned_cos" ] = "sqrt_euclid", + filter: Optional[Any] = None, **kwargs: Any, ) -> List[Document]: return [ doc - for doc, score in self.similarity_search_with_score_by_vector( + for doc, _score in self.similarity_search_with_score_by_vector( embedding, k, distance_func, **kwargs ) ] @@ -254,7 +239,7 @@ def similarity_search( query_vector = self._embedding.embed_query(query) return [ doc - for doc, score in self.similarity_search_with_score_by_vector( + for doc, _score in self.similarity_search_with_score_by_vector( query_vector, k, distance_func, **kwargs ) ] From ee378a0f40c23fc978b070a5b0ba5982a0eb9afd Mon Sep 17 00:00:00 2001 From: Averi Kitsch <akitsch@google.com> Date: Mon, 15 Jan 2024 11:42:33 -0800 Subject: [PATCH 024/215] docs: add page for Firestore Chat Message History integration (#15554) - **Description:** Adds documentation for the `FirestoreChatMessageHistory` integration and lists integration in Google's documentation - **Issue:** NA - **Dependencies:** No --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- .../firestore_chat_message_history.ipynb | 147 ++++++++++++++++++ docs/docs/integrations/platforms/google.mdx | 45 ++++-- 2 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 docs/docs/integrations/memory/firestore_chat_message_history.ipynb diff --git a/docs/docs/integrations/memory/firestore_chat_message_history.ipynb b/docs/docs/integrations/memory/firestore_chat_message_history.ipynb new file mode 100644 index 0000000000000..8bdd586cd4896 --- /dev/null +++ b/docs/docs/integrations/memory/firestore_chat_message_history.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "91c6a7ef", + "metadata": {}, + "source": [ + "# Google Cloud Firestore\n", + "\n", + "> [`Cloud Firestore`](https://cloud.google.com/firestore) is a NoSQL document database built for automatic scaling, high performance, and ease of application development.\n", + "\n", + "This notebook goes over how to use Firestore to store chat message history." + ] + }, + { + "cell_type": "markdown", + "id": "2d6ed3c8-b70a-498c-bc9e-41b91797d3b7", + "metadata": {}, + "source": [ + "## Setting up" + ] + }, + { + "cell_type": "markdown", + "id": "b8eca282", + "metadata": {}, + "source": [ + "To run this notebook, you will need a Google Cloud Project, a Firestore database instance in Native Mode, and Google credentials, see [Firestore Quickstarts](https://cloud.google.com/firestore/docs/quickstarts)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a7f3b3f-d9b8-4577-a7ef-bdd8ecaedb70", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install firebase-admin" + ] + }, + { + "cell_type": "markdown", + "id": "a8e63850-3e14-46fe-a59e-be6d6bf8fe61", + "metadata": {}, + "source": [ + "## Basic Usage" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d15e3302", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.chat_message_histories.firestore import (\n", + " FirestoreChatMessageHistory,\n", + ")\n", + "\n", + "message_history = FirestoreChatMessageHistory(\n", + " collection_name=\"langchain-chat-history\",\n", + " session_id=\"user-session-id\",\n", + " user_id=\"user-id\",\n", + ")\n", + "\n", + "message_history.add_user_message(\"hi!\")\n", + "message_history.add_ai_message(\"whats up?\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "64fc465e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[HumanMessage(content='hi!'),\n", + " HumanMessage(content='hi!'),\n", + " AIMessage(content='whats up?')]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "message_history.messages" + ] + }, + { + "cell_type": "markdown", + "id": "4be8576e", + "metadata": {}, + "source": [ + "## Custom Firestore Client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12999273", + "metadata": {}, + "outputs": [], + "source": [ + "import firebase_admin\n", + "from firebase_admin import credentials, firestore\n", + "\n", + "# Use a service account.\n", + "cred = credentials.Certificate(\"path/to/serviceAccount.json\")\n", + "\n", + "app = firebase_admin.initialize_app(cred)\n", + "client = firestore.client(app=app)\n", + "\n", + "message_history = FirestoreChatMessageHistory(\n", + " collection_name=\"langchain-chat-history\",\n", + " session_id=\"user-session-id\",\n", + " user_id=\"user-id\",\n", + " firestore_client=client,\n", + ")" + ] + } + ], + "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.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/integrations/platforms/google.mdx b/docs/docs/integrations/platforms/google.mdx index 040ace8aac286..ddafdb318f55f 100644 --- a/docs/docs/integrations/platforms/google.mdx +++ b/docs/docs/integrations/platforms/google.mdx @@ -186,7 +186,7 @@ from langchain_community.document_loaders import GoogleSpeechToTextLoader ### Google Vertex AI Vector Search > [Google Vertex AI Vector Search](https://cloud.google.com/vertex-ai/docs/matching-engine/overview), -> formerly known as `Vertex AI Matching Engine`, provides the industry's leading high-scale +> formerly known as `Vertex AI Matching Engine`, provides the industry's leading high-scale > low latency vector database. These vector databases are commonly > referred to as vector similarity-matching or an approximate nearest neighbor (ANN) service. @@ -207,7 +207,7 @@ from langchain_community.vectorstores import MatchingEngine > [Google BigQuery](https://cloud.google.com/bigquery), > BigQuery is a serverless and cost-effective enterprise data warehouse in Google Cloud. > -> Google BigQuery Vector Search +> Google BigQuery Vector Search > BigQuery vector search lets you use GoogleSQL to do semantic search, using vector indexes for fast but approximate results, or using brute force for exact results. > It can calculate Euclidean or Cosine distance. With LangChain, we default to use Euclidean distance. @@ -228,7 +228,7 @@ from langchain.vectorstores import BigQueryVectorSearch >[Google ScaNN](https://github.com/google-research/google-research/tree/master/scann) > (Scalable Nearest Neighbors) is a python package. -> +> >`ScaNN` is a method for efficient vector similarity search at scale. >`ScaNN` includes search space pruning and quantization for Maximum Inner @@ -285,9 +285,9 @@ from langchain.retrievers import GoogleVertexAISearchRetriever ### Document AI Warehouse > [Google Cloud Document AI Warehouse](https://cloud.google.com/document-ai-warehouse) -> allows enterprises to search, store, govern, and manage documents and their AI-extracted +> allows enterprises to search, store, govern, and manage documents and their AI-extracted > data and metadata in a single platform. -> +> ```python from langchain.retrievers import GoogleDocumentAIWarehouseRetriever @@ -304,9 +304,9 @@ documents = docai_wh_retriever.get_relevant_documents( ### Google Cloud Text-to-Speech ->[Google Cloud Text-to-Speech](https://cloud.google.com/text-to-speech) enables developers to -> synthesize natural-sounding speech with 100+ voices, available in multiple languages and variants. -> It applies DeepMind’s groundbreaking research in WaveNet and Google’s powerful neural networks +>[Google Cloud Text-to-Speech](https://cloud.google.com/text-to-speech) enables developers to +> synthesize natural-sounding speech with 100+ voices, available in multiple languages and variants. +> It applies DeepMind’s groundbreaking research in WaveNet and Google’s powerful neural networks > to deliver the highest fidelity possible. We need to install a python package. @@ -354,7 +354,7 @@ from langchain.tools import GooglePlacesTool ### Google Search - Set up a Custom Search Engine, following [these instructions](https://stackoverflow.com/questions/37083058/programmatically-searching-google-in-python-using-custom-search) -- Get an API Key and Custom Search Engine ID from the previous step, and set them as environment variables +- Get an API Key and Custom Search Engine ID from the previous step, and set them as environment variables `GOOGLE_API_KEY` and `GOOGLE_CSE_ID` respectively. ```python @@ -444,12 +444,12 @@ from langchain_community.utilities.google_trends import GoogleTrendsAPIWrapper ### Google Document AI ->[Document AI](https://cloud.google.com/document-ai/docs/overview) is a `Google Cloud Platform` -> service that transforms unstructured data from documents into structured data, making it easier +>[Document AI](https://cloud.google.com/document-ai/docs/overview) is a `Google Cloud Platform` +> service that transforms unstructured data from documents into structured data, making it easier > to understand, analyze, and consume. -We need to set up a [`GCS` bucket and create your own OCR processor](https://cloud.google.com/document-ai/docs/create-processor) -The `GCS_OUTPUT_PATH` should be a path to a folder on GCS (starting with `gs://`) +We need to set up a [`GCS` bucket and create your own OCR processor](https://cloud.google.com/document-ai/docs/create-processor) +The `GCS_OUTPUT_PATH` should be a path to a folder on GCS (starting with `gs://`) and a processor name should look like `projects/PROJECT_NUMBER/locations/LOCATION/processors/PROCESSOR_ID`. We can get it either programmatically or copy from the `Prediction endpoint` section of the `Processor details` tab in the Google Cloud Console. @@ -507,6 +507,23 @@ See a [usage example and authorization instructions](/docs/integrations/toolkits from langchain_community.agent_toolkits import GmailToolkit ``` +## Memory + +### Cloud Firestore + +> [`Cloud Firestore`](https://cloud.google.com/firestore) is a NoSQL document database built for automatic scaling, high performance, and ease of application development. + +First, we need to install the python package. + +```bash +pip install firebase-admin +``` + +See a [usage example and authorization instructions](/docs/integrations/memory/firestore_chat_message_history). + +```python +from langchain_community.chat_message_histories.firestore import FirestoreChatMessageHistory +``` ## Chat Loaders @@ -560,7 +577,7 @@ from langchain_community.utilities import GoogleSerperAPIWrapper ### YouTube >[YouTube Search](https://github.com/joetats/youtube_search) package searches `YouTube` videos avoiding using their heavily rate-limited API. -> +> >It uses the form on the YouTube homepage and scrapes the resulting page. We need to install a python package. From 722012436892bf4d5cea526d771b0848456d2f16 Mon Sep 17 00:00:00 2001 From: Funkeke <153349156+Funkeke@users.noreply.github.com> Date: Tue, 16 Jan 2024 03:43:13 +0800 Subject: [PATCH 025/215] community[patch]: fix tongyi completion and params error (#15544) fix tongyi completion json parse error and prompt's params error --------- Co-authored-by: fangkeke <3339698829@qq.com> Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- libs/community/langchain_community/chat_models/tongyi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/community/langchain_community/chat_models/tongyi.py b/libs/community/langchain_community/chat_models/tongyi.py index be4373a50563b..004ffdd3b12bf 100644 --- a/libs/community/langchain_community/chat_models/tongyi.py +++ b/libs/community/langchain_community/chat_models/tongyi.py @@ -315,9 +315,7 @@ async def _agenerate( ) resp = await asyncio.get_running_loop().run_in_executor( None, - functools.partial( - self.completion_with_retry, **{"run_manager": run_manager, **params} - ), + functools.partial(self.completion_with_retry, **params), ) generations.append( ChatGeneration(**self._chat_generation_from_qwen_resp(resp)) From 251afda5494924df343dc52ce244cb28eba8a320 Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk <139469471+MateuszOssGit@users.noreply.github.com> Date: Mon, 15 Jan 2024 20:44:57 +0100 Subject: [PATCH 026/215] community[patch]: fix stop (stop_sequences) param on WatsonxLLM (#15541) - **Description:** Fix to IBM [watsonx.ai](https://www.ibm.com/products/watsonx-ai) LLM provider (stop (`stop_sequences`) param on watsonxLLM) - **Dependencies:** [ibm-watsonx-ai](https://pypi.org/project/ibm-watsonx-ai/), --- .../langchain_community/llms/watsonxllm.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/libs/community/langchain_community/llms/watsonxllm.py b/libs/community/langchain_community/llms/watsonxllm.py index 380628f5ef31c..d60c728460059 100644 --- a/libs/community/langchain_community/llms/watsonxllm.py +++ b/libs/community/langchain_community/llms/watsonxllm.py @@ -249,6 +249,12 @@ def get_count_value(key: str, result: Dict[str, Any]) -> int: "input_token_count": input_token_count, } + def _get_chat_params(self, stop: Optional[List[str]] = None) -> Dict[str, Any]: + params: Dict[str, Any] = {**self.params} if self.params else None + if stop is not None: + params = (params or {}) | {"stop_sequences": stop} + return params + def _create_llm_result(self, response: List[dict]) -> LLMResult: """Create the LLMResult from the choices and prompts.""" generations = [] @@ -334,11 +340,7 @@ def _generate( response = watsonx_llm.generate(["What is a molecule"]) """ - if stop: - if self.params: - self.params.update({"stop_sequences": stop}) - else: - self.params = {"stop_sequences": stop} + params = self._get_chat_params(stop=stop) should_stream = stream if stream is not None else self.streaming if should_stream: if len(prompts) > 1: @@ -360,7 +362,7 @@ def _generate( return LLMResult(generations=[[generation]], llm_output=llm_output) return LLMResult(generations=[[generation]]) else: - response = self.watsonx_model.generate(prompt=prompts, params=self.params) + response = self.watsonx_model.generate(prompt=prompts, params=params) return self._create_llm_result(response) def _stream( @@ -384,13 +386,9 @@ def _stream( for chunk in response: print(chunk, end='') """ - if stop: - if self.params: - self.params.update({"stop_sequences": stop}) - else: - self.params = {"stop_sequences": stop} + params = self._get_chat_params(stop=stop) for stream_resp in self.watsonx_model.generate_text_stream( - prompt=prompt, raw_response=True, params=self.params + prompt=prompt, raw_response=True, params=params ): chunk = self._stream_response_to_generation_chunk(stream_resp) yield chunk From d334efc848bab5794873c6b787af246ae8e801fc Mon Sep 17 00:00:00 2001 From: chyroc <chyroc@qq.com> Date: Tue, 16 Jan 2024 03:59:39 +0800 Subject: [PATCH 027/215] community[patch]: fix top_p type hint (#15452) fix: https://github.com/langchain-ai/langchain/issues/15341 @efriis --- libs/community/langchain_community/embeddings/ollama.py | 2 +- libs/community/langchain_community/llms/nlpcloud.py | 2 +- libs/community/langchain_community/llms/ollama.py | 2 +- .../partners/google-genai/langchain_google_genai/chat_models.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/community/langchain_community/embeddings/ollama.py b/libs/community/langchain_community/embeddings/ollama.py index 9b9830fb0a0aa..f1c28f1124e7b 100644 --- a/libs/community/langchain_community/embeddings/ollama.py +++ b/libs/community/langchain_community/embeddings/ollama.py @@ -97,7 +97,7 @@ class OllamaEmbeddings(BaseModel, Embeddings): will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)""" - top_p: Optional[int] = None + top_p: Optional[float] = None """Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)""" diff --git a/libs/community/langchain_community/llms/nlpcloud.py b/libs/community/langchain_community/llms/nlpcloud.py index bdff6404290eb..a73087918ce78 100644 --- a/libs/community/langchain_community/llms/nlpcloud.py +++ b/libs/community/langchain_community/llms/nlpcloud.py @@ -38,7 +38,7 @@ class NLPCloud(LLM): """Whether or not to remove the end sequence token.""" bad_words: List[str] = [] """List of tokens not allowed to be generated.""" - top_p: int = 1 + top_p: float = 1.0 """Total probability mass of tokens to consider at each step.""" top_k: int = 50 """The number of highest probability tokens to keep for top-k filtering.""" diff --git a/libs/community/langchain_community/llms/ollama.py b/libs/community/langchain_community/llms/ollama.py index 078edcf092885..29a724b07e911 100644 --- a/libs/community/langchain_community/llms/ollama.py +++ b/libs/community/langchain_community/llms/ollama.py @@ -90,7 +90,7 @@ class _OllamaCommon(BaseLanguageModel): will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)""" - top_p: Optional[int] = None + top_p: Optional[float] = None """Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)""" diff --git a/libs/partners/google-genai/langchain_google_genai/chat_models.py b/libs/partners/google-genai/langchain_google_genai/chat_models.py index d226aeefc4f64..62cb707cdca8c 100644 --- a/libs/partners/google-genai/langchain_google_genai/chat_models.py +++ b/libs/partners/google-genai/langchain_google_genai/chat_models.py @@ -443,7 +443,7 @@ class ChatGoogleGenerativeAI(BaseChatModel): top_k: Optional[int] = None """Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.""" - top_p: Optional[int] = None + top_p: Optional[float] = None """The maximum cumulative probability of tokens to consider when sampling. The model uses combined Top-k and nucleus sampling. From 5cf06db3b38d129ae34e99e9245751f09302e745 Mon Sep 17 00:00:00 2001 From: Zhichao HAN <hanzhichao2000@gmail.com> Date: Tue, 16 Jan 2024 04:27:19 +0800 Subject: [PATCH 028/215] community[minor]: add JsonRequestsWrapper tool (#15374) **Description:** This new feature enhances the flexibility of pipeline integration, particularly when working with RESTful APIs. ``JsonRequestsWrapper`` allows for the decoding of JSON output, instead of the only option for text output. --------- Co-authored-by: Zhichao HAN <hanzhichao2000@hotmail.com> --- docs/docs/integrations/tools/requests.ipynb | 57 +++++++++- .../tools/requests/tool.py | 26 ++--- .../langchain_community/utilities/requests.py | 97 ++++++++++++----- .../unit_tests/tools/requests/test_tool.py | 100 +++++++++++++++++- 4 files changed, 238 insertions(+), 42 deletions(-) diff --git a/docs/docs/integrations/tools/requests.ipynb b/docs/docs/integrations/tools/requests.ipynb index 763bc0e022a63..0f41f8ea98993 100644 --- a/docs/docs/integrations/tools/requests.ipynb +++ b/docs/docs/integrations/tools/requests.ipynb @@ -113,11 +113,64 @@ "requests.get(\"https://www.google.com\")" ] }, + { + "cell_type": "markdown", + "id": "4b0bf1d0", + "metadata": {}, + "source": [ + "If you need the output to be decoded from JSON, you can use the ``JsonRequestsWrapper``." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "3f27ee3d", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Type - <class 'dict'>\n", + "\n", + "Content: \n", + "```\n", + "{'count': 5707, 'name': 'jackson', 'age': 38}\n", + "```\n", + "\n", + "\n" + ] + } + ], + "source": [ + "from langchain_community.utilities.requests import JsonRequestsWrapper\n", + "\n", + "requests = JsonRequestsWrapper()\n", + "\n", + "\n", + "rval = requests.get(\"https://api.agify.io/?name=jackson\")\n", + "\n", + "print(\n", + " f\"\"\"\n", + "\n", + "Type - {type(rval)}\n", + "\n", + "Content: \n", + "```\n", + "{rval}\n", + "```\n", + "\n", + "\"\"\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52a1aa15", + "metadata": {}, "outputs": [], "source": [] } @@ -138,7 +191,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.2" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/libs/community/langchain_community/tools/requests/tool.py b/libs/community/langchain_community/tools/requests/tool.py index ae2a2ca9546d9..17985d5aa3a25 100644 --- a/libs/community/langchain_community/tools/requests/tool.py +++ b/libs/community/langchain_community/tools/requests/tool.py @@ -1,7 +1,7 @@ # flake8: noqa """Tools for making requests to an API endpoint.""" import json -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Union from langchain_core.pydantic_v1 import BaseModel from langchain_core.callbacks import ( @@ -9,7 +9,7 @@ CallbackManagerForToolRun, ) -from langchain_community.utilities.requests import TextRequestsWrapper +from langchain_community.utilities.requests import GenericRequestsWrapper from langchain_core.tools import BaseTool @@ -26,7 +26,7 @@ def _clean_url(url: str) -> str: class BaseRequestsTool(BaseModel): """Base class for requests tools.""" - requests_wrapper: TextRequestsWrapper + requests_wrapper: GenericRequestsWrapper class RequestsGetTool(BaseRequestsTool, BaseTool): @@ -37,7 +37,7 @@ class RequestsGetTool(BaseRequestsTool, BaseTool): def _run( self, url: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool.""" return self.requests_wrapper.get(_clean_url(url)) @@ -45,7 +45,7 @@ async def _arun( self, url: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool asynchronously.""" return await self.requests_wrapper.aget(_clean_url(url)) @@ -64,7 +64,7 @@ class RequestsPostTool(BaseRequestsTool, BaseTool): def _run( self, text: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool.""" try: data = _parse_input(text) @@ -76,7 +76,7 @@ async def _arun( self, text: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool asynchronously.""" try: data = _parse_input(text) @@ -101,7 +101,7 @@ class RequestsPatchTool(BaseRequestsTool, BaseTool): def _run( self, text: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool.""" try: data = _parse_input(text) @@ -113,7 +113,7 @@ async def _arun( self, text: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool asynchronously.""" try: data = _parse_input(text) @@ -138,7 +138,7 @@ class RequestsPutTool(BaseRequestsTool, BaseTool): def _run( self, text: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool.""" try: data = _parse_input(text) @@ -150,7 +150,7 @@ async def _arun( self, text: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool asynchronously.""" try: data = _parse_input(text) @@ -171,7 +171,7 @@ def _run( self, url: str, run_manager: Optional[CallbackManagerForToolRun] = None, - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool.""" return self.requests_wrapper.delete(_clean_url(url)) @@ -179,6 +179,6 @@ async def _arun( self, url: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - ) -> str: + ) -> Union[str, Dict[str, Any]]: """Run the tool asynchronously.""" return await self.requests_wrapper.adelete(_clean_url(url)) diff --git a/libs/community/langchain_community/utilities/requests.py b/libs/community/langchain_community/utilities/requests.py index 651616ff53183..673df3d5e57fe 100644 --- a/libs/community/langchain_community/utilities/requests.py +++ b/libs/community/langchain_community/utilities/requests.py @@ -1,10 +1,11 @@ """Lightweight wrapper around requests library, with async support.""" from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator, Dict, Optional +from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union import aiohttp import requests from langchain_core.pydantic_v1 import BaseModel, Extra +from requests import Response class Requests(BaseModel): @@ -108,15 +109,13 @@ async def adelete( yield response -class TextRequestsWrapper(BaseModel): - """Lightweight wrapper around requests library. - - The main purpose of this wrapper is to always return a text output. - """ +class GenericRequestsWrapper(BaseModel): + """Lightweight wrapper around requests library.""" headers: Optional[Dict[str, str]] = None aiosession: Optional[aiohttp.ClientSession] = None auth: Optional[Any] = None + response_content_type: Literal["text", "json"] = "text" class Config: """Configuration for this pydantic object.""" @@ -130,50 +129,96 @@ def requests(self) -> Requests: headers=self.headers, aiosession=self.aiosession, auth=self.auth ) - def get(self, url: str, **kwargs: Any) -> str: + def _get_resp_content(self, response: Response) -> Union[str, Dict[str, Any]]: + if self.response_content_type == "text": + return response.text + elif self.response_content_type == "json": + return response.json() + else: + raise ValueError(f"Invalid return type: {self.response_content_type}") + + def _aget_resp_content( + self, response: aiohttp.ClientResponse + ) -> Union[str, Dict[str, Any]]: + if self.response_content_type == "text": + return response.text() + elif self.response_content_type == "json": + return response.json() + else: + raise ValueError(f"Invalid return type: {self.response_content_type}") + + def get(self, url: str, **kwargs: Any) -> Union[str, Dict[str, Any]]: """GET the URL and return the text.""" - return self.requests.get(url, **kwargs).text + return self._get_resp_content(self.requests.get(url, **kwargs)) - def post(self, url: str, data: Dict[str, Any], **kwargs: Any) -> str: + def post( + self, url: str, data: Dict[str, Any], **kwargs: Any + ) -> Union[str, Dict[str, Any]]: """POST to the URL and return the text.""" - return self.requests.post(url, data, **kwargs).text + return self._get_resp_content(self.requests.post(url, data, **kwargs)) - def patch(self, url: str, data: Dict[str, Any], **kwargs: Any) -> str: + def patch( + self, url: str, data: Dict[str, Any], **kwargs: Any + ) -> Union[str, Dict[str, Any]]: """PATCH the URL and return the text.""" - return self.requests.patch(url, data, **kwargs).text + return self._get_resp_content(self.requests.patch(url, data, **kwargs)) - def put(self, url: str, data: Dict[str, Any], **kwargs: Any) -> str: + def put( + self, url: str, data: Dict[str, Any], **kwargs: Any + ) -> Union[str, Dict[str, Any]]: """PUT the URL and return the text.""" - return self.requests.put(url, data, **kwargs).text + return self._get_resp_content(self.requests.put(url, data, **kwargs)) - def delete(self, url: str, **kwargs: Any) -> str: + def delete(self, url: str, **kwargs: Any) -> Union[str, Dict[str, Any]]: """DELETE the URL and return the text.""" - return self.requests.delete(url, **kwargs).text + return self._get_resp_content(self.requests.delete(url, **kwargs)) - async def aget(self, url: str, **kwargs: Any) -> str: + async def aget(self, url: str, **kwargs: Any) -> Union[str, Dict[str, Any]]: """GET the URL and return the text asynchronously.""" async with self.requests.aget(url, **kwargs) as response: - return await response.text() + return await self._aget_resp_content(response) - async def apost(self, url: str, data: Dict[str, Any], **kwargs: Any) -> str: + async def apost( + self, url: str, data: Dict[str, Any], **kwargs: Any + ) -> Union[str, Dict[str, Any]]: """POST to the URL and return the text asynchronously.""" async with self.requests.apost(url, data, **kwargs) as response: - return await response.text() + return await self._aget_resp_content(response) - async def apatch(self, url: str, data: Dict[str, Any], **kwargs: Any) -> str: + async def apatch( + self, url: str, data: Dict[str, Any], **kwargs: Any + ) -> Union[str, Dict[str, Any]]: """PATCH the URL and return the text asynchronously.""" async with self.requests.apatch(url, data, **kwargs) as response: - return await response.text() + return await self._aget_resp_content(response) - async def aput(self, url: str, data: Dict[str, Any], **kwargs: Any) -> str: + async def aput( + self, url: str, data: Dict[str, Any], **kwargs: Any + ) -> Union[str, Dict[str, Any]]: """PUT the URL and return the text asynchronously.""" async with self.requests.aput(url, data, **kwargs) as response: - return await response.text() + return await self._aget_resp_content(response) - async def adelete(self, url: str, **kwargs: Any) -> str: + async def adelete(self, url: str, **kwargs: Any) -> Union[str, Dict[str, Any]]: """DELETE the URL and return the text asynchronously.""" async with self.requests.adelete(url, **kwargs) as response: - return await response.text() + return await self._aget_resp_content(response) + + +class JsonRequestsWrapper(GenericRequestsWrapper): + """Lightweight wrapper around requests library, with async support. + + The main purpose of this wrapper is to always return a json output.""" + + response_content_type: Literal["text", "json"] = "json" + + +class TextRequestsWrapper(GenericRequestsWrapper): + """Lightweight wrapper around requests library, with async support. + + The main purpose of this wrapper is to always return a text output.""" + + response_content_type: Literal["text", "json"] = "text" # For backwards compatibility diff --git a/libs/community/tests/unit_tests/tools/requests/test_tool.py b/libs/community/tests/unit_tests/tools/requests/test_tool.py index 3ced01aea8d28..b2d53dcc6661c 100644 --- a/libs/community/tests/unit_tests/tools/requests/test_tool.py +++ b/libs/community/tests/unit_tests/tools/requests/test_tool.py @@ -1,4 +1,5 @@ import asyncio +import json from typing import Any, Dict import pytest @@ -11,7 +12,10 @@ RequestsPutTool, _parse_input, ) -from langchain_community.utilities.requests import TextRequestsWrapper +from langchain_community.utilities.requests import ( + JsonRequestsWrapper, + TextRequestsWrapper, +) class _MockTextRequestsWrapper(TextRequestsWrapper): @@ -98,3 +102,97 @@ def test_requests_delete_tool(mock_requests_wrapper: TextRequestsWrapper) -> Non tool = RequestsDeleteTool(requests_wrapper=mock_requests_wrapper) assert tool.run("https://example.com") == "delete_response" assert asyncio.run(tool.arun("https://example.com")) == "adelete_response" + + +class _MockJsonRequestsWrapper(JsonRequestsWrapper): + @staticmethod + def get(url: str, **kwargs: Any) -> Dict[str, Any]: + return {"response": "get_response"} + + @staticmethod + async def aget(url: str, **kwargs: Any) -> Dict[str, Any]: + return {"response": "aget_response"} + + @staticmethod + def post(url: str, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"response": f"post {json.dumps(data)}"} + + @staticmethod + async def apost(url: str, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"response": f"apost {json.dumps(data)}"} + + @staticmethod + def patch(url: str, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"response": f"patch {json.dumps(data)}"} + + @staticmethod + async def apatch(url: str, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"response": f"apatch {json.dumps(data)}"} + + @staticmethod + def put(url: str, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"response": f"put {json.dumps(data)}"} + + @staticmethod + async def aput(url: str, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"response": f"aput {json.dumps(data)}"} + + @staticmethod + def delete(url: str, **kwargs: Any) -> Dict[str, Any]: + return {"response": "delete_response"} + + @staticmethod + async def adelete(url: str, **kwargs: Any) -> Dict[str, Any]: + return {"response": "adelete_response"} + + +@pytest.fixture +def mock_json_requests_wrapper() -> JsonRequestsWrapper: + return _MockJsonRequestsWrapper() + + +def test_requests_get_tool_json( + mock_json_requests_wrapper: JsonRequestsWrapper, +) -> None: + tool = RequestsGetTool(requests_wrapper=mock_json_requests_wrapper) + assert tool.run("https://example.com") == {"response": "get_response"} + assert asyncio.run(tool.arun("https://example.com")) == { + "response": "aget_response" + } + + +def test_requests_post_tool_json( + mock_json_requests_wrapper: JsonRequestsWrapper, +) -> None: + tool = RequestsPostTool(requests_wrapper=mock_json_requests_wrapper) + input_text = '{"url": "https://example.com", "data": {"key": "value"}}' + assert tool.run(input_text) == {"response": 'post {"key": "value"}'} + assert asyncio.run(tool.arun(input_text)) == {"response": 'apost {"key": "value"}'} + + +def test_requests_patch_tool_json( + mock_json_requests_wrapper: JsonRequestsWrapper, +) -> None: + tool = RequestsPatchTool(requests_wrapper=mock_json_requests_wrapper) + input_text = '{"url": "https://example.com", "data": {"key": "value"}}' + assert tool.run(input_text) == {"response": 'patch {"key": "value"}'} + assert asyncio.run(tool.arun(input_text)) == {"response": 'apatch {"key": "value"}'} + + +def test_requests_put_tool_json( + mock_json_requests_wrapper: JsonRequestsWrapper, +) -> None: + tool = RequestsPutTool(requests_wrapper=mock_json_requests_wrapper) + input_text = '{"url": "https://example.com", "data": {"key": "value"}}' + assert tool.run(input_text) == {"response": 'put {"key": "value"}'} + assert asyncio.run(tool.arun(input_text)) == {"response": 'aput {"key": "value"}'} + + +def test_requests_delete_tool_json( + mock_json_requests_wrapper: JsonRequestsWrapper, +) -> None: + tool = RequestsDeleteTool(requests_wrapper=mock_json_requests_wrapper) + assert tool.run("https://example.com") == {"response": "delete_response"} + assert asyncio.run(tool.arun("https://example.com")) == { + "response": "adelete_response" + } From d19664681121def24aea6dce1ce9a7c8f7c1c466 Mon Sep 17 00:00:00 2001 From: andrijdavid <david@geek.mg> Date: Mon, 15 Jan 2024 21:29:14 +0100 Subject: [PATCH 029/215] community[patch]: Refactor OpenAIWhisperParserLocal (#15150) This PR addresses an issue in OpenAIWhisperParserLocal where requesting CUDA without availability leads to an AttributeError #15143 Changes: - Refactored Logic for CUDA Availability: The initialization now includes a check for CUDA availability. If CUDA is not available, the code falls back to using the CPU. This ensures seamless operation without manual intervention. - Parameterizing Batch Size and Chunk Size: The batch_size and chunk_size are now configurable parameters, offering greater flexibility and optimization options based on the specific requirements of the use case. --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- .../document_loaders/parsers/audio.py | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/parsers/audio.py b/libs/community/langchain_community/document_loaders/parsers/audio.py index ab54c67ed3751..3b96f9860c536 100644 --- a/libs/community/langchain_community/document_loaders/parsers/audio.py +++ b/libs/community/langchain_community/document_loaders/parsers/audio.py @@ -64,7 +64,7 @@ def lazy_parse(self, blob: Blob) -> Iterator[Document]: file_obj.name = f"part_{split_number}.mp3" # Transcribe - print(f"Transcribing part {split_number+1}!") + print(f"Transcribing part {split_number + 1}!") attempts = 0 while attempts < 3: try: @@ -116,6 +116,8 @@ def __init__( self, device: str = "0", lang_model: Optional[str] = None, + batch_size: int = 8, + chunk_length: int = 30, forced_decoder_ids: Optional[Tuple[Dict]] = None, ): """Initialize the parser. @@ -126,6 +128,10 @@ def __init__( Defaults to None. forced_decoder_ids: id states for decoder in a multilanguage model. Defaults to None. + batch_size: batch size used for decoding + Defaults to 8. + chunk_length: chunk length used during inference. + Defaults to 30s. """ try: from transformers import pipeline @@ -141,47 +147,37 @@ def __init__( "torch package not found, please install it with " "`pip install torch`" ) - # set device, cpu by default check if there is a GPU available + # Determine the device to use if device == "cpu": self.device = "cpu" - if lang_model is not None: - self.lang_model = lang_model - print("WARNING! Model override. Using model: ", self.lang_model) - else: - # unless overridden, use the small base model on cpu - self.lang_model = "openai/whisper-base" else: - if torch.cuda.is_available(): - self.device = "cuda:0" - # check GPU memory and select automatically the model - mem = torch.cuda.get_device_properties(self.device).total_memory / ( - 1024**2 - ) - if mem < 5000: - rec_model = "openai/whisper-base" - elif mem < 7000: - rec_model = "openai/whisper-small" - elif mem < 12000: - rec_model = "openai/whisper-medium" - else: - rec_model = "openai/whisper-large" - - # check if model is overridden - if lang_model is not None: - self.lang_model = lang_model - print("WARNING! Model override. Might not fit in your GPU") - else: - self.lang_model = rec_model + self.device = "cuda:0" if torch.cuda.is_available() else "cpu" + + if self.device == "cpu": + default_model = "openai/whisper-base" + self.lang_model = lang_model if lang_model else default_model + else: + # Set the language model based on the device and available memory + mem = torch.cuda.get_device_properties(self.device).total_memory / (1024**2) + if mem < 5000: + rec_model = "openai/whisper-base" + elif mem < 7000: + rec_model = "openai/whisper-small" + elif mem < 12000: + rec_model = "openai/whisper-medium" else: - "cpu" + rec_model = "openai/whisper-large" + self.lang_model = lang_model if lang_model else rec_model print("Using the following model: ", self.lang_model) + self.batch_size = batch_size + # load model for inference self.pipe = pipeline( "automatic-speech-recognition", model=self.lang_model, - chunk_length_s=30, + chunk_length_s=chunk_length, device=self.device, ) if forced_decoder_ids is not None: @@ -224,7 +220,7 @@ def lazy_parse(self, blob: Blob) -> Iterator[Document]: y, sr = librosa.load(file_obj, sr=16000) - prediction = self.pipe(y.copy(), batch_size=8)["text"] + prediction = self.pipe(y.copy(), batch_size=self.batch_size)["text"] yield Document( page_content=prediction, From 061e63eef201dc11be1003bc113309769325d847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=BF=9C?= <90301759+19374242@users.noreply.github.com> Date: Tue, 16 Jan 2024 04:34:01 +0800 Subject: [PATCH 030/215] community[minor]: add vikingdb vecstore (#15155) --------- Co-authored-by: gaoyuan <gaoyuan.20001218@bytedance.com> --- .../integrations/vectorstores/vikingdb.ipynb | 248 ++++++++++++ .../vectorstores/vikngdb.py | 375 ++++++++++++++++++ 2 files changed, 623 insertions(+) create mode 100644 docs/docs/integrations/vectorstores/vikingdb.ipynb create mode 100644 libs/community/langchain_community/vectorstores/vikngdb.py diff --git a/docs/docs/integrations/vectorstores/vikingdb.ipynb b/docs/docs/integrations/vectorstores/vikingdb.ipynb new file mode 100644 index 0000000000000..66ab177efd56b --- /dev/null +++ b/docs/docs/integrations/vectorstores/vikingdb.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "96ff9e912bfe9d8", + "metadata": { + "collapsed": false + }, + "source": [ + "# viking DB\n", + "\n", + ">[viking DB](https://www.volcengine.com/docs/6459/1163946) is a database that stores, indexes, and manages massive embedding vectors generated by deep neural networks and other machine learning (ML) models.\n", + "\n", + "This notebook shows how to use functionality related to the VikingDB vector database.\n", + "\n", + "To run, you should have a [viking DB instance up and running](https://www.volcengine.com/docs/6459/1165058).\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd771e02d8a93a0", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "!pip install --upgrade volcengine" + ] + }, + { + "cell_type": "markdown", + "id": "12719205caed0d18", + "metadata": { + "collapsed": false + }, + "source": [ + "We want to use VikingDBEmbeddings so we have to get the VikingDB API Key." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fbfb32665b4a3640", + "metadata": { + "ExecuteTime": { + "end_time": "2023-12-21T09:53:24.186916Z", + "start_time": "2023-12-21T09:53:24.179524Z" + }, + "collapsed": false + }, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8c983d329237fa4", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from langchain.document_loaders import TextLoader\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain.vectorstores.vikingdb import VikingDB, VikingDBConfig\n", + "from langchain_openai import OpenAIEmbeddings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a4aea2eaeb2261", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "loader = TextLoader(\"./test.txt\")\n", + "documents = loader.load()\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=10, chunk_overlap=0)\n", + "docs = text_splitter.split_documents(documents)\n", + "\n", + "embeddings = OpenAIEmbeddings()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfd593f3deabfaf8", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "db = VikingDB.from_documents(\n", + " docs,\n", + " embeddings,\n", + " connection_args=VikingDBConfig(\n", + " host=\"host\", region=\"region\", ak=\"ak\", sk=\"sk\", scheme=\"http\"\n", + " ),\n", + " drop_old=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "50e6ee12ca7eec39", + "metadata": { + "ExecuteTime": { + "end_time": "2023-12-21T10:01:47.355894Z", + "start_time": "2023-12-21T10:01:47.334789Z" + }, + "collapsed": false + }, + "outputs": [], + "source": [ + "query = \"What did the president say about Ketanji Brown Jackson\"\n", + "docs = db.similarity_search(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b6b81f5995c79ef0", + "metadata": { + "ExecuteTime": { + "end_time": "2023-12-21T10:01:47.771478Z", + "start_time": "2023-12-21T10:01:47.731485Z" + }, + "collapsed": false + }, + "outputs": [], + "source": [ + "docs[0].page_content" + ] + }, + { + "cell_type": "markdown", + "id": "a2d932c1290478ee", + "metadata": { + "collapsed": false + }, + "source": [ + "### Compartmentalize the data with viking DB Collections\n", + "\n", + "You can store different unrelated documents in different collections within same viking DB instance to maintain the context" + ] + }, + { + "cell_type": "markdown", + "id": "907de4eb10626d2a", + "metadata": { + "collapsed": false + }, + "source": [ + "Here's how you can create a new collection" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f5a59ba40f7985f", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "db = VikingDB.from_documents(\n", + " docs,\n", + " embeddings,\n", + " connection_args=VikingDBConfig(\n", + " host=\"host\", region=\"region\", ak=\"ak\", sk=\"sk\", scheme=\"http\"\n", + " ),\n", + " collection_name=\"collection_1\",\n", + " drop_old=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7c8eada37b17d992", + "metadata": { + "collapsed": false + }, + "source": [ + "And here is how you retrieve that stored collection" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "883ec678d47c9adc", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "db = VikingDB.from_documents(\n", + " embeddings,\n", + " connection_args=VikingDBConfig(\n", + " host=\"host\", region=\"region\", ak=\"ak\", sk=\"sk\", scheme=\"http\"\n", + " ),\n", + " collection_name=\"collection_1\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2f0be30cfe70083d", + "metadata": { + "collapsed": false + }, + "source": [ + "After retreival you can go on querying it as usual." + ] + } + ], + "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/community/langchain_community/vectorstores/vikngdb.py b/libs/community/langchain_community/vectorstores/vikngdb.py new file mode 100644 index 0000000000000..2f235f0bf4cbd --- /dev/null +++ b/libs/community/langchain_community/vectorstores/vikngdb.py @@ -0,0 +1,375 @@ +from __future__ import annotations + +import logging +import uuid +from typing import Any, List, Optional, Tuple + +import numpy as np +from langchain_core.documents import Document +from langchain_core.embeddings import Embeddings +from langchain_core.vectorstores import VectorStore + +from langchain_community.vectorstores.utils import maximal_marginal_relevance + +logger = logging.getLogger(__name__) + + +class VikingDBConfig(object): + def __init__(self, host="host", region="region", ak="ak", sk="sk", scheme="http"): + self.host = host + self.region = region + self.ak = ak + self.sk = sk + self.scheme = scheme + + +class VikingDB(VectorStore): + def __init__( + self, + embedding_function: Embeddings, + collection_name: str = "LangChainCollection", + connection_args: Optional[VikingDBConfig] = None, + index_params: Optional[dict] = None, + drop_old: Optional[bool] = False, + **kwargs: Any, + ): + try: + from volcengine.viking_db import Collection, VikingDBService + except ImportError: + raise ValueError( + "Could not import volcengine python package. " + "Please install it with `pip install --upgrade volcengine`." + ) + self.embedding_func = embedding_function + self.collection_name = collection_name + self.index_name = "LangChainIndex" + self.connection_args = connection_args + self.index_params = index_params + self.drop_old = drop_old + self.service = VikingDBService( + connection_args.host, + connection_args.region, + connection_args.ak, + connection_args.sk, + connection_args.scheme, + ) + + try: + col = self.service.get_collection(collection_name) + except Exception: + col = None + self.collection = col + self.index = None + if self.collection is not None: + self.index = self.service.get_index(self.collection_name, self.index_name) + + if drop_old and isinstance(self.collection, Collection): + indexes = self.service.list_indexes(collection_name) + for index in indexes: + self.service.drop_index(collection_name, index.index_name) + self.service.drop_collection(collection_name) + self.collection = None + self.index = None + + @property + def embeddings(self) -> Embeddings: + return self.embedding_func + + def _create_collection( + self, embeddings: List, metadatas: Optional[List[dict]] = None + ) -> None: + try: + from volcengine.viking_db import Field, FieldType + except ImportError: + raise ValueError( + "Could not import volcengine python package. " + "Please install it with `pip install --upgrade volcengine`." + ) + dim = len(embeddings[0]) + fields = [] + if metadatas: + for key, value in metadatas[0].items(): + # print(key, value) + if isinstance(value, str): + fields.append(Field(key, FieldType.String)) + if isinstance(value, int): + fields.append(Field(key, FieldType.Int64)) + if isinstance(value, bool): + fields.append(Field(key, FieldType.Bool)) + if isinstance(value, list) and all( + isinstance(item, str) for item in value + ): + fields.append(Field(key, FieldType.List_String)) + if isinstance(value, list) and all( + isinstance(item, int) for item in value + ): + fields.append(Field(key, FieldType.List_Int64)) + fields.append(Field("text", FieldType.String)) + + fields.append(Field("primary_key", FieldType.String, is_primary_key=True)) + + fields.append(Field("vector", FieldType.Vector, dim=dim)) + + self.collection = self.service.create_collection(self.collection_name, fields) + + def _create_index(self) -> None: + try: + from volcengine.viking_db import VectorIndexParams + except ImportError: + raise ValueError( + "Could not import volcengine python package. " + "Please install it with `pip install --upgrade volcengine`." + ) + cpu_quota = 2 + vector_index = VectorIndexParams() + partition_by = "" + scalar_index = None + if self.index_params is not None: + if self.index_params.get("cpu_quota") is not None: + cpu_quota = self.index_params["cpu_quota"] + if self.index_params.get("vector_index") is not None: + vector_index = self.index_params["vector_index"] + if self.index_params.get("partition_by") is not None: + partition_by = self.index_params["partition_by"] + if self.index_params.get("scalar_index") is not None: + scalar_index = self.index_params["scalar_index"] + + self.index = self.service.create_index( + self.collection_name, + self.index_name, + vector_index=vector_index, + cpu_quota=cpu_quota, + partition_by=partition_by, + scalar_index=scalar_index, + ) + + def add_texts( + self, + texts: List[str], + metadatas: Optional[List[dict]] = None, + batch_size: int = 1000, + **kwargs: Any, + ) -> List[str]: + try: + from volcengine.viking_db import Data + except ImportError: + raise ValueError( + "Could not import volcengine python package. " + "Please install it with `pip install --upgrade volcengine`." + ) + texts = list(texts) + try: + embeddings = self.embedding_func.embed_documents(texts) + except NotImplementedError: + embeddings = [self.embedding_func.embed_query(x) for x in texts] + if len(embeddings) == 0: + logger.debug("Nothing to insert, skipping.") + return [] + if self.collection is None: + self._create_collection(embeddings, metadatas) + self._create_index() + + # insert data + data = [] + pks: List[str] = [] + for index in range(len(embeddings)): + primary_key = str(uuid.uuid4()) + pks.append(primary_key) + field = { + "text": texts[index], + "primary_key": primary_key, + "vector": embeddings[index], + } + if metadatas is not None and index < len(metadatas): + names = list(metadatas[index].keys()) + for name in names: + field[name] = metadatas[index].get(name) + data.append(Data(field)) + + total_count = len(data) + for i in range(0, total_count, batch_size): + end = min(i + batch_size, total_count) + insert_data = data[i:end] + # print(insert_data) + self.collection.upsert_data(insert_data) + return pks + + def similarity_search( + self, + query: str, + params: Optional[dict] = None, + **kwargs: Any, + ) -> List[Document]: + res = self.similarity_search_with_score(query=query, params=params, **kwargs) + return [doc for doc, _ in res] + + def similarity_search_with_score( + self, + query: str, + params: Optional[dict] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + embedding = self.embedding_func.embed_query(query) + + res = self.similarity_search_with_score_by_vector( + embedding=embedding, params=params, **kwargs + ) + return res + + def similarity_search_by_vector( + self, + embedding: List[float], + params: Optional[dict] = None, + **kwargs: Any, + ) -> List[Document]: + res = self.similarity_search_with_score_by_vector( + embedding=embedding, params=params, **kwargs + ) + return [doc for doc, _ in res] + + def similarity_search_with_score_by_vector( + self, + embedding: List[float], + params: Optional[dict] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + if self.collection is None: + logger.debug("No existing collection to search.") + return [] + + filter = None + limit = 10 + output_fields = None + partition = "default" + if params is not None: + if params.get("filter") is not None: + filter = params["filter"] + if params.get("limit") is not None: + limit = params["limit"] + if params.get("output_fields") is not None: + output_fields = params["output_fields"] + if params.get("partition") is not None: + partition = params["partition"] + + res = self.index.search_by_vector( + embedding, + filter=filter, + limit=limit, + output_fields=output_fields, + partition=partition, + ) + + ret = [] + for item in res: + item.fields.pop("primary_key") + item.fields.pop("vector") + page_content = item.fields.pop("text") + doc = Document(page_content=page_content, metadata=item.fields) + pair = (doc, item.score) + ret.append(pair) + return ret + + def max_marginal_relevance_search( + self, + query: str, + k: int = 4, + lambda_mult: float = 0.5, + params: Optional[dict] = None, + **kwargs: Any, + ) -> List[Document]: + embedding = self.embedding_func.embed_query(query) + return self.max_marginal_relevance_search_by_vector( + embedding=embedding, + k=k, + lambda_mult=lambda_mult, + params=params, + **kwargs, + ) + + def max_marginal_relevance_search_by_vector( + self, + embedding: List[float], + k: int = 4, + lambda_mult: float = 0.5, + params: Optional[dict] = None, + **kwargs: Any, + ) -> List[Document]: + if self.collection is None: + logger.debug("No existing collection to search.") + return [] + filter = None + limit = 10 + output_fields = None + partition = "default" + if params is not None: + if params.get("filter") is not None: + filter = params["filter"] + if params.get("limit") is not None: + limit = params["limit"] + if params.get("output_fields") is not None: + output_fields = params["output_fields"] + if params.get("partition") is not None: + partition = params["partition"] + + res = self.index.search_by_vector( + embedding, + filter=filter, + limit=limit, + output_fields=output_fields, + partition=partition, + ) + documents = [] + ordered_result_embeddings = [] + for item in res: + ordered_result_embeddings.append(item.fields.pop("vector")) + item.fields.pop("primary_key") + page_content = item.fields.pop("text") + doc = Document(page_content=page_content, metadata=item.fields) + documents.append(doc) + + new_ordering = maximal_marginal_relevance( + np.array(embedding), ordered_result_embeddings, k=k, lambda_mult=lambda_mult + ) + # Reorder the values and return. + ret = [] + for x in new_ordering: + # Function can return -1 index + if x == -1: + break + else: + ret.append(documents[x]) + return ret + + def delete( + self, + ids: Optional[List[str]] = None, + **kwargs: Any, + ) -> None: + if self.collection is None: + logger.debug("No existing collection to search.") + self.collection.delete_data(ids) + + @classmethod + def from_texts( + cls, + texts: List[str], + embedding: Embeddings, + connection_args: Optional[VikingDBConfig] = None, + metadatas: Optional[List[dict]] = None, + collection_name: str = "LangChainCollection", + index_params: Optional[dict] = None, + drop_old: bool = False, + **kwargs: Any, + ): + if connection_args is None: + raise Exception("VikingDBConfig does not exists") + vector_db = cls( + embedding_function=embedding, + collection_name=collection_name, + connection_args=connection_args, + index_params=index_params, + drop_old=drop_old, + **kwargs, + ) + vector_db.add_texts(texts=texts, metadatas=metadatas) + return vector_db From 697a6f2c80800a9dc3cd2ddbc0c1a7c69ab679a2 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:54:30 -0800 Subject: [PATCH 031/215] langchain[patch]: fix requests lint (#16049) --- libs/langchain/langchain/chains/api/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/langchain/langchain/chains/api/base.py b/libs/langchain/langchain/chains/api/base.py index 0afd7bd383cf5..05b18330efc67 100644 --- a/libs/langchain/langchain/chains/api/base.py +++ b/libs/langchain/langchain/chains/api/base.py @@ -163,7 +163,7 @@ def _call( ) api_response = self.requests_wrapper.get(api_url) _run_manager.on_text( - api_response, color="yellow", end="\n", verbose=self.verbose + str(api_response), color="yellow", end="\n", verbose=self.verbose ) answer = self.api_answer_chain.predict( question=question, @@ -198,7 +198,7 @@ async def _acall( ) api_response = await self.requests_wrapper.aget(api_url) await _run_manager.on_text( - api_response, color="yellow", end="\n", verbose=self.verbose + str(api_response), color="yellow", end="\n", verbose=self.verbose ) answer = await self.api_answer_chain.apredict( question=question, From 476fb328ee0b93b53d3c7cd6ed2cc6510767fb82 Mon Sep 17 00:00:00 2001 From: Antonio Morales <35844399+AntonioMorales97@users.noreply.github.com> Date: Tue, 16 Jan 2024 04:57:09 +0100 Subject: [PATCH 032/215] community[patch]: implement adelete from VectorStore in Qdrant (#16005) **Description:** Implement `adelete` function from `VectorStore` in `Qdrant` to support other asynchronous flows such as async indexing (`aindex`) which requires `adelete` to be implemented. Since `Qdrant` can be passed an async qdrant client, this can be supported easily. --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- .../vectorstores/qdrant.py | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/qdrant.py b/libs/community/langchain_community/vectorstores/qdrant.py index 5073943819b4a..ce64d054d1aa7 100644 --- a/libs/community/langchain_community/vectorstores/qdrant.py +++ b/libs/community/langchain_community/vectorstores/qdrant.py @@ -1138,8 +1138,7 @@ def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> Optional[boo **kwargs: Other keyword arguments that subclasses might use. Returns: - Optional[bool]: True if deletion is successful, - False otherwise, None if not implemented. + True if deletion is successful, False otherwise. """ from qdrant_client.http import models as rest @@ -1149,6 +1148,37 @@ def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> Optional[boo ) return result.status == rest.UpdateStatus.COMPLETED + @sync_call_fallback + async def adelete( + self, ids: Optional[List[str]] = None, **kwargs: Any + ) -> Optional[bool]: + """Delete by vector ID or other criteria. + + Args: + ids: List of ids to delete. + **kwargs: Other keyword arguments that subclasses might use. + + Returns: + True if deletion is successful, False otherwise. + """ + from qdrant_client.local.async_qdrant_local import AsyncQdrantLocal + + if self.async_client is None or isinstance( + self.async_client._client, AsyncQdrantLocal + ): + raise NotImplementedError( + "QdrantLocal cannot interoperate with sync and async clients" + ) + + from qdrant_client.http import models as rest + + result = await self.async_client.delete( + collection_name=self.collection_name, + points_selector=ids, + ) + + return result.status == rest.UpdateStatus.COMPLETED + @classmethod def from_texts( cls: Type[Qdrant], From ca288d8f2c8b63769b61dda786526acbbf7d1eea Mon Sep 17 00:00:00 2001 From: James Briggs <35938317+jamescalam@users.noreply.github.com> Date: Tue, 16 Jan 2024 14:12:19 +0000 Subject: [PATCH 033/215] community[patch]: add vector param to index query for pinecone vec store (#16054) --- libs/community/langchain_community/vectorstores/pinecone.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/pinecone.py b/libs/community/langchain_community/vectorstores/pinecone.py index 9e67ad64f6e92..410d55420fa57 100644 --- a/libs/community/langchain_community/vectorstores/pinecone.py +++ b/libs/community/langchain_community/vectorstores/pinecone.py @@ -201,7 +201,7 @@ def similarity_search_by_vector_with_score( namespace = self._namespace docs = [] results = self._index.query( - [embedding], + vector=[embedding], top_k=k, include_metadata=True, namespace=namespace, @@ -299,7 +299,7 @@ def max_marginal_relevance_search_by_vector( if namespace is None: namespace = self._namespace results = self._index.query( - [embedding], + vector=[embedding], top_k=fetch_k, include_values=True, include_metadata=True, From 52114bdfaca706f006eabcef5782017c309eb44a Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Tue, 16 Jan 2024 06:25:28 -0800 Subject: [PATCH 034/215] community[patch]: release 0.0.13 (#16087) --- libs/community/poetry.lock | 2 +- libs/community/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/poetry.lock b/libs/community/poetry.lock index db439c02327f2..b56f0701c6a6c 100644 --- a/libs/community/poetry.lock +++ b/libs/community/poetry.lock @@ -3881,7 +3881,7 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.9" +version = "0.1.10" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" diff --git a/libs/community/pyproject.toml b/libs/community/pyproject.toml index 3c92ca76ff795..c7b01b6217c7a 100644 --- a/libs/community/pyproject.toml +++ b/libs/community/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-community" -version = "0.0.12" +version = "0.0.13" description = "Community contributed LangChain integrations." authors = [] license = "MIT" From 770f57196edea164f8638de960577306e70104d9 Mon Sep 17 00:00:00 2001 From: Nuno Campos <nuno@langchain.dev> Date: Tue, 16 Jan 2024 09:22:52 -0800 Subject: [PATCH 035/215] Add unit test for overridden lc_namespace (#16093) --- .../tests/unit_tests/load/__snapshots__/test_dump.ambr | 7 +++---- libs/langchain/tests/unit_tests/load/test_dump.py | 7 ++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libs/langchain/tests/unit_tests/load/__snapshots__/test_dump.ambr b/libs/langchain/tests/unit_tests/load/__snapshots__/test_dump.ambr index 2cf2fdf8cd258..0540760d4ce9d 100644 --- a/libs/langchain/tests/unit_tests/load/__snapshots__/test_dump.ambr +++ b/libs/langchain/tests/unit_tests/load/__snapshots__/test_dump.ambr @@ -30,10 +30,9 @@ "lc": 1, "type": "constructor", "id": [ - "tests", - "unit_tests", - "load", - "test_dump", + "my", + "special", + "namespace", "SpecialPerson" ], "kwargs": { diff --git a/libs/langchain/tests/unit_tests/load/test_dump.py b/libs/langchain/tests/unit_tests/load/test_dump.py index d428b04c71e06..0d0a354172c87 100644 --- a/libs/langchain/tests/unit_tests/load/test_dump.py +++ b/libs/langchain/tests/unit_tests/load/test_dump.py @@ -1,6 +1,6 @@ """Test for Serializable base class""" -from typing import Any, Dict +from typing import Any, Dict, List import pytest from langchain_community.chat_models.openai import ChatOpenAI @@ -37,6 +37,10 @@ class SpecialPerson(Person): another_visible: str = "bye" + @classmethod + def get_lc_namespace(cls) -> List[str]: + return ["my", "special", "namespace"] + # Gets merged with parent class's secrets @property def lc_secrets(self) -> Dict[str, str]: @@ -58,6 +62,7 @@ def test_person(snapshot: Any) -> None: sp = SpecialPerson(another_secret="Wooo", secret="Hmm") assert dumps(sp, pretty=True) == snapshot assert Person.lc_id() == ["tests", "unit_tests", "load", "test_dump", "Person"] + assert SpecialPerson.lc_id() == ["my", "special", "namespace", "SpecialPerson"] def test_typeerror() -> None: From c5656a4905c94aeb5bc874157bb8f9bdc8cd284e Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:36:43 -0800 Subject: [PATCH 036/215] core[patch]: pass exceptions to fallbacks (#16048) --- libs/core/langchain_core/runnables/base.py | 6 + .../langchain_core/runnables/fallbacks.py | 194 ++++++--- .../__snapshots__/test_fallbacks.ambr | 373 ++++++++++++++++++ .../__snapshots__/test_runnable.ambr | 274 ------------- .../unit_tests/runnables/test_fallbacks.py | 231 +++++++++++ .../unit_tests/runnables/test_runnable.py | 47 --- 6 files changed, 743 insertions(+), 382 deletions(-) create mode 100644 libs/core/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr create mode 100644 libs/core/tests/unit_tests/runnables/test_fallbacks.py diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index 0bb5ddb11b953..5ac7ce0dac415 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -923,12 +923,17 @@ def with_fallbacks( fallbacks: Sequence[Runnable[Input, Output]], *, exceptions_to_handle: Tuple[Type[BaseException], ...] = (Exception,), + exception_key: Optional[str] = None, ) -> RunnableWithFallbacksT[Input, Output]: """Add fallbacks to a runnable, returning a new Runnable. Args: fallbacks: A sequence of runnables to try if the original runnable fails. exceptions_to_handle: A tuple of exception types to handle. + exception_key: If string is specified then handled exceptions will be passed + to fallbacks as part of the input under the specified key. If None, + exceptions will not be passed to fallbacks. If used, the base runnable + and its fallbacks must accept a dictionary as input. Returns: A new Runnable that will try the original runnable, and then each @@ -940,6 +945,7 @@ def with_fallbacks( runnable=self, fallbacks=fallbacks, exceptions_to_handle=exceptions_to_handle, + exception_key=exception_key, ) """ --- Helper methods for Subclasses --- """ diff --git a/libs/core/langchain_core/runnables/fallbacks.py b/libs/core/langchain_core/runnables/fallbacks.py index 5f6dbf11bf378..7f8ab1f86637f 100644 --- a/libs/core/langchain_core/runnables/fallbacks.py +++ b/libs/core/langchain_core/runnables/fallbacks.py @@ -2,6 +2,7 @@ from typing import ( TYPE_CHECKING, Any, + Dict, Iterator, List, Optional, @@ -9,6 +10,7 @@ Tuple, Type, Union, + cast, ) from langchain_core.load.dump import dumpd @@ -89,6 +91,11 @@ def when_all_is_lost(inputs): Any exception that is not a subclass of these exceptions will be raised immediately. """ + exception_key: Optional[str] = None + """If string is specified then handled exceptions will be passed to fallbacks as + part of the input under the specified key. If None, exceptions + will not be passed to fallbacks. If used, the base runnable and its fallbacks + must accept a dictionary as input.""" class Config: arbitrary_types_allowed = True @@ -136,6 +143,11 @@ def runnables(self) -> Iterator[Runnable[Input, Output]]: def invoke( self, input: Input, config: Optional[RunnableConfig] = None, **kwargs: Any ) -> Output: + if self.exception_key is not None and not isinstance(input, dict): + raise ValueError( + "If 'exception_key' is specified then input must be a dictionary." + f"However found a type of {type(input)} for input" + ) # setup callbacks config = ensure_config(config) callback_manager = get_callback_manager_for_config(config) @@ -144,8 +156,11 @@ def invoke( dumpd(self), input, name=config.get("run_name") ) first_error = None + last_error = None for runnable in self.runnables: try: + if self.exception_key and last_error is not None: + input[self.exception_key] = last_error output = runnable.invoke( input, patch_config(config, callbacks=run_manager.get_child()), @@ -154,6 +169,7 @@ def invoke( except self.exceptions_to_handle as e: if first_error is None: first_error = e + last_error = e except BaseException as e: run_manager.on_chain_error(e) raise e @@ -171,6 +187,11 @@ async def ainvoke( config: Optional[RunnableConfig] = None, **kwargs: Optional[Any], ) -> Output: + if self.exception_key is not None and not isinstance(input, dict): + raise ValueError( + "If 'exception_key' is specified then input must be a dictionary." + f"However found a type of {type(input)} for input" + ) # setup callbacks config = ensure_config(config) callback_manager = get_async_callback_manager_for_config(config) @@ -180,8 +201,11 @@ async def ainvoke( ) first_error = None + last_error = None for runnable in self.runnables: try: + if self.exception_key and last_error is not None: + input[self.exception_key] = last_error output = await runnable.ainvoke( input, patch_config(config, callbacks=run_manager.get_child()), @@ -190,6 +214,7 @@ async def ainvoke( except self.exceptions_to_handle as e: if first_error is None: first_error = e + last_error = e except BaseException as e: await run_manager.on_chain_error(e) raise e @@ -211,8 +236,13 @@ def batch( ) -> List[Output]: from langchain_core.callbacks.manager import CallbackManager - if return_exceptions: - raise NotImplementedError() + if self.exception_key is not None and not all( + isinstance(input, dict) for input in inputs + ): + raise ValueError( + "If 'exception_key' is specified then inputs must be dictionaries." + f"However found a type of {type(inputs[0])} for input" + ) if not inputs: return [] @@ -241,35 +271,51 @@ def batch( for cm, input, config in zip(callback_managers, inputs, configs) ] - first_error = None + to_return: Dict[int, Any] = {} + run_again = {i: input for i, input in enumerate(inputs)} + handled_exceptions: Dict[int, BaseException] = {} + first_to_raise = None for runnable in self.runnables: - try: - outputs = runnable.batch( - inputs, - [ - # each step a child run of the corresponding root run - patch_config(config, callbacks=rm.get_child()) - for rm, config in zip(run_managers, configs) - ], - return_exceptions=return_exceptions, - **kwargs, - ) - except self.exceptions_to_handle as e: - if first_error is None: - first_error = e - except BaseException as e: - for rm in run_managers: - rm.on_chain_error(e) - raise e - else: - for rm, output in zip(run_managers, outputs): - rm.on_chain_end(output) - return outputs - if first_error is None: - raise ValueError("No error stored at end of fallbacks.") - for rm in run_managers: - rm.on_chain_error(first_error) - raise first_error + outputs = runnable.batch( + [input for _, input in sorted(run_again.items())], + [ + # each step a child run of the corresponding root run + patch_config(configs[i], callbacks=run_managers[i].get_child()) + for i in sorted(run_again) + ], + return_exceptions=True, + **kwargs, + ) + for (i, input), output in zip(sorted(run_again.copy().items()), outputs): + if isinstance(output, BaseException) and not isinstance( + output, self.exceptions_to_handle + ): + if not return_exceptions: + first_to_raise = first_to_raise or output + else: + handled_exceptions[i] = cast(BaseException, output) + run_again.pop(i) + elif isinstance(output, self.exceptions_to_handle): + if self.exception_key: + input[self.exception_key] = output # type: ignore + handled_exceptions[i] = cast(BaseException, output) + else: + run_managers[i].on_chain_end(output) + to_return[i] = output + run_again.pop(i) + handled_exceptions.pop(i, None) + if first_to_raise: + raise first_to_raise + if not run_again: + break + + sorted_handled_exceptions = sorted(handled_exceptions.items()) + for i, error in sorted_handled_exceptions: + run_managers[i].on_chain_error(error) + if not return_exceptions and sorted_handled_exceptions: + raise sorted_handled_exceptions[0][1] + to_return.update(handled_exceptions) + return [output for _, output in sorted(to_return.items())] async def abatch( self, @@ -281,8 +327,13 @@ async def abatch( ) -> List[Output]: from langchain_core.callbacks.manager import AsyncCallbackManager - if return_exceptions: - raise NotImplementedError() + if self.exception_key is not None and not all( + isinstance(input, dict) for input in inputs + ): + raise ValueError( + "If 'exception_key' is specified then inputs must be dictionaries." + f"However found a type of {type(inputs[0])} for input" + ) if not inputs: return [] @@ -313,33 +364,54 @@ async def abatch( ) ) - first_error = None + to_return = {} + run_again = {i: input for i, input in enumerate(inputs)} + handled_exceptions: Dict[int, BaseException] = {} + first_to_raise = None for runnable in self.runnables: - try: - outputs = await runnable.abatch( - inputs, - [ - # each step a child run of the corresponding root run - patch_config(config, callbacks=rm.get_child()) - for rm, config in zip(run_managers, configs) - ], - return_exceptions=return_exceptions, - **kwargs, - ) - except self.exceptions_to_handle as e: - if first_error is None: - first_error = e - except BaseException as e: - await asyncio.gather(*(rm.on_chain_error(e) for rm in run_managers)) - else: - await asyncio.gather( - *( - rm.on_chain_end(output) - for rm, output in zip(run_managers, outputs) - ) - ) - return outputs - if first_error is None: - raise ValueError("No error stored at end of fallbacks.") - await asyncio.gather(*(rm.on_chain_error(first_error) for rm in run_managers)) - raise first_error + outputs = await runnable.abatch( + [input for _, input in sorted(run_again.items())], + [ + # each step a child run of the corresponding root run + patch_config(configs[i], callbacks=run_managers[i].get_child()) + for i in sorted(run_again) + ], + return_exceptions=True, + **kwargs, + ) + + for (i, input), output in zip(sorted(run_again.copy().items()), outputs): + if isinstance(output, BaseException) and not isinstance( + output, self.exceptions_to_handle + ): + if not return_exceptions: + first_to_raise = first_to_raise or output + else: + handled_exceptions[i] = cast(BaseException, output) + run_again.pop(i) + elif isinstance(output, self.exceptions_to_handle): + if self.exception_key: + input[self.exception_key] = output # type: ignore + handled_exceptions[i] = cast(BaseException, output) + else: + to_return[i] = output + await run_managers[i].on_chain_end(output) + run_again.pop(i) + handled_exceptions.pop(i, None) + + if first_to_raise: + raise first_to_raise + if not run_again: + break + + sorted_handled_exceptions = sorted(handled_exceptions.items()) + await asyncio.gather( + *( + run_managers[i].on_chain_error(error) + for i, error in sorted_handled_exceptions + ) + ) + if not return_exceptions and sorted_handled_exceptions: + raise sorted_handled_exceptions[0][1] + to_return.update(handled_exceptions) + return [output for _, output in sorted(to_return.items())] # type: ignore diff --git a/libs/core/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr b/libs/core/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr new file mode 100644 index 0000000000000..751274bf7682c --- /dev/null +++ b/libs/core/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr @@ -0,0 +1,373 @@ +# serializer version: 1 +# name: test_fallbacks[chain] + ''' + { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableSequence" + ], + "kwargs": { + "first": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableParallel" + ], + "kwargs": { + "steps": { + "buz": { + "lc": 1, + "type": "not_implemented", + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "repr": "RunnableLambda(lambda x: x)" + } + } + } + }, + "middle": [], + "last": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableWithFallbacks" + ], + "kwargs": { + "runnable": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableSequence" + ], + "kwargs": { + "first": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "prompts", + "prompt", + "PromptTemplate" + ], + "kwargs": { + "input_variables": [ + "buz" + ], + "template": "what did baz say to {buz}", + "template_format": "f-string", + "partial_variables": {} + } + }, + "middle": [], + "last": { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['foo'], i=1)" + }, + "name": null + } + }, + "fallbacks": [ + { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableSequence" + ], + "kwargs": { + "first": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "prompts", + "prompt", + "PromptTemplate" + ], + "kwargs": { + "input_variables": [ + "buz" + ], + "template": "what did baz say to {buz}", + "template_format": "f-string", + "partial_variables": {} + } + }, + "middle": [], + "last": { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['bar'])" + }, + "name": null + } + } + ], + "exceptions_to_handle": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "builtins", + "Exception" + ], + "repr": "<class 'Exception'>" + } + ], + "exception_key": null + } + }, + "name": null + } + } + ''' +# --- +# name: test_fallbacks[chain_pass_exceptions] + ''' + { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableSequence" + ], + "kwargs": { + "first": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableParallel" + ], + "kwargs": { + "steps": { + "text": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnablePassthrough" + ], + "kwargs": { + "func": null, + "afunc": null, + "input_type": null + } + } + } + } + }, + "middle": [], + "last": { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableWithFallbacks" + ], + "kwargs": { + "runnable": { + "lc": 1, + "type": "not_implemented", + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "repr": "RunnableLambda(_raise_error)" + }, + "fallbacks": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "repr": "RunnableLambda(_dont_raise_error)" + } + ], + "exceptions_to_handle": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "builtins", + "Exception" + ], + "repr": "<class 'Exception'>" + } + ], + "exception_key": "exception" + } + }, + "name": null + } + } + ''' +# --- +# name: test_fallbacks[llm] + ''' + { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableWithFallbacks" + ], + "kwargs": { + "runnable": { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['foo'], i=1)" + }, + "fallbacks": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['bar'])" + } + ], + "exceptions_to_handle": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "builtins", + "Exception" + ], + "repr": "<class 'Exception'>" + } + ], + "exception_key": null + } + } + ''' +# --- +# name: test_fallbacks[llm_multi] + ''' + { + "lc": 1, + "type": "constructor", + "id": [ + "langchain", + "schema", + "runnable", + "RunnableWithFallbacks" + ], + "kwargs": { + "runnable": { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['foo'], i=1)" + }, + "fallbacks": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['baz'], i=1)" + }, + { + "lc": 1, + "type": "not_implemented", + "id": [ + "tests", + "unit_tests", + "fake", + "llm", + "FakeListLLM" + ], + "repr": "FakeListLLM(responses=['bar'])" + } + ], + "exceptions_to_handle": [ + { + "lc": 1, + "type": "not_implemented", + "id": [ + "builtins", + "Exception" + ], + "repr": "<class 'Exception'>" + } + ], + "exception_key": null + } + } + ''' +# --- diff --git a/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr b/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr index 8ff1fe98dbb91..051520c0457f6 100644 --- a/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr +++ b/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr @@ -696,280 +696,6 @@ } ''' # --- -# name: test_llm_with_fallbacks[llm_chain_with_fallbacks] - ''' - { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableSequence" - ], - "kwargs": { - "first": { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableParallel" - ], - "kwargs": { - "steps": { - "buz": { - "lc": 1, - "type": "not_implemented", - "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" - ], - "repr": "RunnableLambda(lambda x: x)" - } - } - } - }, - "middle": [], - "last": { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableWithFallbacks" - ], - "kwargs": { - "runnable": { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableSequence" - ], - "kwargs": { - "first": { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "kwargs": { - "input_variables": [ - "buz" - ], - "template": "what did baz say to {buz}", - "template_format": "f-string", - "partial_variables": {} - } - }, - "middle": [], - "last": { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['foo'], i=1)" - }, - "name": null - } - }, - "fallbacks": [ - { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableSequence" - ], - "kwargs": { - "first": { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "kwargs": { - "input_variables": [ - "buz" - ], - "template": "what did baz say to {buz}", - "template_format": "f-string", - "partial_variables": {} - } - }, - "middle": [], - "last": { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['bar'])" - }, - "name": null - } - } - ], - "exceptions_to_handle": [ - { - "lc": 1, - "type": "not_implemented", - "id": [ - "builtins", - "Exception" - ], - "repr": "<class 'Exception'>" - } - ] - } - }, - "name": null - } - } - ''' -# --- -# name: test_llm_with_fallbacks[llm_with_fallbacks] - ''' - { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableWithFallbacks" - ], - "kwargs": { - "runnable": { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['foo'], i=1)" - }, - "fallbacks": [ - { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['bar'])" - } - ], - "exceptions_to_handle": [ - { - "lc": 1, - "type": "not_implemented", - "id": [ - "builtins", - "Exception" - ], - "repr": "<class 'Exception'>" - } - ] - } - } - ''' -# --- -# name: test_llm_with_fallbacks[llm_with_multi_fallbacks] - ''' - { - "lc": 1, - "type": "constructor", - "id": [ - "langchain", - "schema", - "runnable", - "RunnableWithFallbacks" - ], - "kwargs": { - "runnable": { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['foo'], i=1)" - }, - "fallbacks": [ - { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['baz'], i=1)" - }, - { - "lc": 1, - "type": "not_implemented", - "id": [ - "tests", - "unit_tests", - "fake", - "llm", - "FakeListLLM" - ], - "repr": "FakeListLLM(responses=['bar'])" - } - ], - "exceptions_to_handle": [ - { - "lc": 1, - "type": "not_implemented", - "id": [ - "builtins", - "Exception" - ], - "repr": "<class 'Exception'>" - } - ] - } - } - ''' -# --- # name: test_prompt_with_chat_model ''' ChatPromptTemplate(input_variables=['question'], messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a nice assistant.')), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['question'], template='{question}'))]) diff --git a/libs/core/tests/unit_tests/runnables/test_fallbacks.py b/libs/core/tests/unit_tests/runnables/test_fallbacks.py new file mode 100644 index 0000000000000..ecd9cb6fc9f83 --- /dev/null +++ b/libs/core/tests/unit_tests/runnables/test_fallbacks.py @@ -0,0 +1,231 @@ +import sys +from typing import Any + +import pytest +from syrupy import SnapshotAssertion + +from langchain_core.load import dumps +from langchain_core.prompts import PromptTemplate +from langchain_core.runnables import ( + Runnable, + RunnableLambda, + RunnableParallel, + RunnablePassthrough, + RunnableWithFallbacks, +) +from tests.unit_tests.fake.llm import FakeListLLM + + +@pytest.fixture() +def llm() -> RunnableWithFallbacks: + error_llm = FakeListLLM(responses=["foo"], i=1) + pass_llm = FakeListLLM(responses=["bar"]) + + return error_llm.with_fallbacks([pass_llm]) + + +@pytest.fixture() +def llm_multi() -> RunnableWithFallbacks: + error_llm = FakeListLLM(responses=["foo"], i=1) + error_llm_2 = FakeListLLM(responses=["baz"], i=1) + pass_llm = FakeListLLM(responses=["bar"]) + + return error_llm.with_fallbacks([error_llm_2, pass_llm]) + + +@pytest.fixture() +def chain() -> Runnable: + error_llm = FakeListLLM(responses=["foo"], i=1) + pass_llm = FakeListLLM(responses=["bar"]) + + prompt = PromptTemplate.from_template("what did baz say to {buz}") + return RunnableParallel({"buz": lambda x: x}) | (prompt | error_llm).with_fallbacks( + [prompt | pass_llm] + ) + + +def _raise_error(inputs: dict) -> str: + raise ValueError() + + +def _dont_raise_error(inputs: dict) -> str: + if "exception" in inputs: + return "bar" + raise ValueError() + + +@pytest.fixture() +def chain_pass_exceptions() -> Runnable: + fallback = RunnableLambda(_dont_raise_error) + return {"text": RunnablePassthrough()} | RunnableLambda( + _raise_error + ).with_fallbacks([fallback], exception_key="exception") + + +@pytest.mark.parametrize( + "runnable", + ["llm", "llm_multi", "chain", "chain_pass_exceptions"], +) +async def test_fallbacks( + runnable: RunnableWithFallbacks, request: Any, snapshot: SnapshotAssertion +) -> None: + runnable = request.getfixturevalue(runnable) + assert runnable.invoke("hello") == "bar" + assert runnable.batch(["hi", "hey", "bye"]) == ["bar"] * 3 + assert list(runnable.stream("hello")) == ["bar"] + assert await runnable.ainvoke("hello") == "bar" + assert await runnable.abatch(["hi", "hey", "bye"]) == ["bar"] * 3 + assert list(await runnable.ainvoke("hello")) == list("bar") + if sys.version_info >= (3, 9): + assert dumps(runnable, pretty=True) == snapshot + + +def _runnable(inputs: dict) -> str: + if inputs["text"] == "foo": + return "first" + if "exception" not in inputs: + raise ValueError() + if inputs["text"] == "bar": + return "second" + if isinstance(inputs["exception"], ValueError): + raise RuntimeError() + return "third" + + +def _assert_potential_error(actual: list, expected: list) -> None: + for x, y in zip(actual, expected): + if isinstance(x, Exception): + assert isinstance(y, type(x)) + else: + assert x == y + + +def test_invoke_with_exception_key() -> None: + runnable = RunnableLambda(_runnable) + runnable_with_single = runnable.with_fallbacks( + [runnable], exception_key="exception" + ) + with pytest.raises(ValueError): + runnable_with_single.invoke({"text": "baz"}) + + actual = runnable_with_single.invoke({"text": "bar"}) + expected = "second" + _assert_potential_error([actual], [expected]) + + runnable_with_double = runnable.with_fallbacks( + [runnable, runnable], exception_key="exception" + ) + actual = runnable_with_double.invoke({"text": "baz"}) + + expected = "third" + _assert_potential_error([actual], [expected]) + + +async def test_ainvoke_with_exception_key() -> None: + runnable = RunnableLambda(_runnable) + runnable_with_single = runnable.with_fallbacks( + [runnable], exception_key="exception" + ) + with pytest.raises(ValueError): + await runnable_with_single.ainvoke({"text": "baz"}) + + actual = await runnable_with_single.ainvoke({"text": "bar"}) + expected = "second" + _assert_potential_error([actual], [expected]) + + runnable_with_double = runnable.with_fallbacks( + [runnable, runnable], exception_key="exception" + ) + actual = await runnable_with_double.ainvoke({"text": "baz"}) + expected = "third" + _assert_potential_error([actual], [expected]) + + +def test_batch() -> None: + runnable = RunnableLambda(_runnable) + with pytest.raises(ValueError): + runnable.batch([{"text": "foo"}, {"text": "bar"}, {"text": "baz"}]) + actual = runnable.batch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + expected = ["first", ValueError(), ValueError()] + _assert_potential_error(actual, expected) + + runnable_with_single = runnable.with_fallbacks( + [runnable], exception_key="exception" + ) + with pytest.raises(RuntimeError): + runnable_with_single.batch([{"text": "foo"}, {"text": "bar"}, {"text": "baz"}]) + actual = runnable_with_single.batch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + expected = ["first", "second", RuntimeError()] + _assert_potential_error(actual, expected) + + runnable_with_double = runnable.with_fallbacks( + [runnable, runnable], exception_key="exception" + ) + actual = runnable_with_double.batch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + + expected = ["first", "second", "third"] + _assert_potential_error(actual, expected) + + runnable_with_double = runnable.with_fallbacks( + [runnable, runnable], + exception_key="exception", + exceptions_to_handle=(ValueError,), + ) + actual = runnable_with_double.batch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + + expected = ["first", "second", RuntimeError()] + _assert_potential_error(actual, expected) + + +async def test_abatch() -> None: + runnable = RunnableLambda(_runnable) + with pytest.raises(ValueError): + await runnable.abatch([{"text": "foo"}, {"text": "bar"}, {"text": "baz"}]) + actual = await runnable.abatch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + expected = ["first", ValueError(), ValueError()] + _assert_potential_error(actual, expected) + + runnable_with_single = runnable.with_fallbacks( + [runnable], exception_key="exception" + ) + with pytest.raises(RuntimeError): + await runnable_with_single.abatch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}] + ) + actual = await runnable_with_single.abatch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + expected = ["first", "second", RuntimeError()] + _assert_potential_error(actual, expected) + + runnable_with_double = runnable.with_fallbacks( + [runnable, runnable], exception_key="exception" + ) + actual = await runnable_with_double.abatch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + + expected = ["first", "second", "third"] + _assert_potential_error(actual, expected) + + runnable_with_double = runnable.with_fallbacks( + [runnable, runnable], + exception_key="exception", + exceptions_to_handle=(ValueError,), + ) + actual = await runnable_with_double.abatch( + [{"text": "foo"}, {"text": "bar"}, {"text": "baz"}], return_exceptions=True + ) + + expected = ["first", "second", RuntimeError()] + _assert_potential_error(actual, expected) diff --git a/libs/core/tests/unit_tests/runnables/test_runnable.py b/libs/core/tests/unit_tests/runnables/test_runnable.py index 2a94cf2469bc3..49e729b595f0f 100644 --- a/libs/core/tests/unit_tests/runnables/test_runnable.py +++ b/libs/core/tests/unit_tests/runnables/test_runnable.py @@ -66,7 +66,6 @@ RunnablePassthrough, RunnablePick, RunnableSequence, - RunnableWithFallbacks, add, chain, ) @@ -3683,52 +3682,6 @@ async def test_runnable_sequence_atransform() -> None: assert "".join(chunks) == "foo-lish" -@pytest.fixture() -def llm_with_fallbacks() -> RunnableWithFallbacks: - error_llm = FakeListLLM(responses=["foo"], i=1) - pass_llm = FakeListLLM(responses=["bar"]) - - return error_llm.with_fallbacks([pass_llm]) - - -@pytest.fixture() -def llm_with_multi_fallbacks() -> RunnableWithFallbacks: - error_llm = FakeListLLM(responses=["foo"], i=1) - error_llm_2 = FakeListLLM(responses=["baz"], i=1) - pass_llm = FakeListLLM(responses=["bar"]) - - return error_llm.with_fallbacks([error_llm_2, pass_llm]) - - -@pytest.fixture() -def llm_chain_with_fallbacks() -> Runnable: - error_llm = FakeListLLM(responses=["foo"], i=1) - pass_llm = FakeListLLM(responses=["bar"]) - - prompt = PromptTemplate.from_template("what did baz say to {buz}") - return RunnableParallel({"buz": lambda x: x}) | (prompt | error_llm).with_fallbacks( - [prompt | pass_llm] - ) - - -@pytest.mark.parametrize( - "runnable", - ["llm_with_fallbacks", "llm_with_multi_fallbacks", "llm_chain_with_fallbacks"], -) -async def test_llm_with_fallbacks( - runnable: RunnableWithFallbacks, request: Any, snapshot: SnapshotAssertion -) -> None: - runnable = request.getfixturevalue(runnable) - assert runnable.invoke("hello") == "bar" - assert runnable.batch(["hi", "hey", "bye"]) == ["bar"] * 3 - assert list(runnable.stream("hello")) == ["bar"] - assert await runnable.ainvoke("hello") == "bar" - assert await runnable.abatch(["hi", "hey", "bye"]) == ["bar"] * 3 - assert list(await runnable.ainvoke("hello")) == list("bar") - if sys.version_info >= (3, 9): - assert dumps(runnable, pretty=True) == snapshot - - class FakeSplitIntoListParser(BaseOutputParser[List[str]]): """Parse the output of an LLM call to a comma-separated list.""" From 076593382adcd02886ff8122e7d5fd571b708e5f Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:46:04 -0800 Subject: [PATCH 037/215] core[patch]: Release 0.1.11 (#16100) --- libs/core/poetry.lock | 22 +++++++++++++++++++++- libs/core/pyproject.toml | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/libs/core/poetry.lock b/libs/core/poetry.lock index 6fa23fd5b4f79..84fa8613b5208 100644 --- a/libs/core/poetry.lock +++ b/libs/core/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -1164,6 +1164,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1943,6 +1953,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1950,8 +1961,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1968,6 +1986,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1975,6 +1994,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 0c7206145a746..caed8502ee1bc 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-core" -version = "0.1.10" +version = "0.1.11" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From 5f057f24ac1a1fe93a77273a6f2359a7f8217864 Mon Sep 17 00:00:00 2001 From: Juan Bustos <bustosjuan@gmail.com> Date: Tue, 16 Jan 2024 12:49:42 -0500 Subject: [PATCH 038/215] docs: Update elasticsearch.ipynb (#16090) Fixed a typo, the parameter used for the Elasticsearch API key was called api_key, but the parameter is called es_api_key. --- docs/docs/integrations/vectorstores/elasticsearch.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/vectorstores/elasticsearch.ipynb b/docs/docs/integrations/vectorstores/elasticsearch.ipynb index 5bbb9a7f47b08..9032e698c2a1c 100644 --- a/docs/docs/integrations/vectorstores/elasticsearch.ipynb +++ b/docs/docs/integrations/vectorstores/elasticsearch.ipynb @@ -75,7 +75,7 @@ " )\n", "```\n", "### Authentication\n", - "For production, we recommend you run with security enabled. To connect with login credentials, you can use the parameters `api_key` or `es_user` and `es_password`.\n", + "For production, we recommend you run with security enabled. To connect with login credentials, you can use the parameters `es_api_key` or `es_user` and `es_password`.\n", "\n", "Example:\n", "```python\n", From 6b6269441cefd97fcf7a7fc31bf715c553cc5af7 Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Tue, 16 Jan 2024 18:50:30 +0100 Subject: [PATCH 039/215] docs: Add page for AstraDB self retriever (#16077) Preview: https://langchain-git-fork-cbornet-astra-self-retriever-docs-langchain.vercel.app/docs/integrations/retrievers/self_query/astradb --- .../retrievers/self_query/astradb.ipynb | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 docs/docs/integrations/retrievers/self_query/astradb.ipynb diff --git a/docs/docs/integrations/retrievers/self_query/astradb.ipynb b/docs/docs/integrations/retrievers/self_query/astradb.ipynb new file mode 100644 index 0000000000000..43386e6a94b47 --- /dev/null +++ b/docs/docs/integrations/retrievers/self_query/astradb.ipynb @@ -0,0 +1,322 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Astra DB\n", + "\n", + "DataStax [Astra DB](https://docs.datastax.com/en/astra/home/astra.html) is a serverless vector-capable database built on Cassandra and made conveniently available through an easy-to-use JSON API.\n", + "\n", + "In the walkthrough, we'll demo the `SelfQueryRetriever` with an `Astra DB` vector store." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating an Astra DB vector store\n", + "First we'll want to create an Astra DB VectorStore and seed it with some data. We've created a small demo set of documents that contain summaries of movies.\n", + "\n", + "NOTE: The self-query retriever requires you to have `lark` installed (`pip install lark`). We also need the `astrapy` package." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet lark astrapy langchain-openai" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to use `OpenAIEmbeddings` so we have to get the OpenAI API Key." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from getpass import getpass\n", + "\n", + "from langchain_openai.embeddings import OpenAIEmbeddings\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass(\"OpenAI API Key:\")\n", + "\n", + "embeddings = OpenAIEmbeddings()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Create the Astra DB VectorStore:\n", + "\n", + "- the API Endpoint looks like `https://01234567-89ab-cdef-0123-456789abcdef-us-east1.apps.astra.datastax.com`\n", + "- the Token looks like `AstraCS:6gBhNmsk135....`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ASTRA_DB_API_ENDPOINT = input(\"ASTRA_DB_API_ENDPOINT = \")\n", + "ASTRA_DB_APPLICATION_TOKEN = getpass(\"ASTRA_DB_APPLICATION_TOKEN = \")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.schema import Document\n", + "from langchain.vectorstores import AstraDB\n", + "\n", + "docs = [\n", + " Document(\n", + " page_content=\"A bunch of scientists bring back dinosaurs and mayhem breaks loose\",\n", + " metadata={\"year\": 1993, \"rating\": 7.7, \"genre\": \"science fiction\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Leo DiCaprio gets lost in a dream within a dream within a dream within a ...\",\n", + " metadata={\"year\": 2010, \"director\": \"Christopher Nolan\", \"rating\": 8.2},\n", + " ),\n", + " Document(\n", + " page_content=\"A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea\",\n", + " metadata={\"year\": 2006, \"director\": \"Satoshi Kon\", \"rating\": 8.6},\n", + " ),\n", + " Document(\n", + " page_content=\"A bunch of normal-sized women are supremely wholesome and some men pine after them\",\n", + " metadata={\"year\": 2019, \"director\": \"Greta Gerwig\", \"rating\": 8.3},\n", + " ),\n", + " Document(\n", + " page_content=\"Toys come alive and have a blast doing so\",\n", + " metadata={\"year\": 1995, \"genre\": \"animated\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Three men walk into the Zone, three men walk out of the Zone\",\n", + " metadata={\n", + " \"year\": 1979,\n", + " \"director\": \"Andrei Tarkovsky\",\n", + " \"genre\": \"science fiction\",\n", + " \"rating\": 9.9,\n", + " },\n", + " ),\n", + "]\n", + "\n", + "vectorstore = AstraDB.from_documents(\n", + " docs,\n", + " embeddings,\n", + " collection_name=\"astra_self_query_demo\",\n", + " api_endpoint=ASTRA_DB_API_ENDPOINT,\n", + " token=ASTRA_DB_APPLICATION_TOKEN,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating our self-querying retriever\n", + "Now we can instantiate our retriever. To do this we'll need to provide some information upfront about the metadata fields that our documents support and a short description of the document contents." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains.query_constructor.base import AttributeInfo\n", + "from langchain.llms import OpenAI\n", + "from langchain.retrievers.self_query.base import SelfQueryRetriever\n", + "\n", + "metadata_field_info = [\n", + " AttributeInfo(\n", + " name=\"genre\",\n", + " description=\"The genre of the movie\",\n", + " type=\"string or list[string]\",\n", + " ),\n", + " AttributeInfo(\n", + " name=\"year\",\n", + " description=\"The year the movie was released\",\n", + " type=\"integer\",\n", + " ),\n", + " AttributeInfo(\n", + " name=\"director\",\n", + " description=\"The name of the movie director\",\n", + " type=\"string\",\n", + " ),\n", + " AttributeInfo(\n", + " name=\"rating\", description=\"A 1-10 rating for the movie\", type=\"float\"\n", + " ),\n", + "]\n", + "document_content_description = \"Brief summary of a movie\"\n", + "llm = OpenAI(temperature=0)\n", + "\n", + "retriever = SelfQueryRetriever.from_llm(\n", + " llm, vectorstore, document_content_description, metadata_field_info, verbose=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Testing it out\n", + "And now we can try actually using our retriever!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example only specifies a relevant query\n", + "retriever.get_relevant_documents(\"What are some movies about dinosaurs?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example specifies a filter\n", + "retriever.get_relevant_documents(\"I want to watch a movie rated higher than 8.5\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example only specifies a query and a filter\n", + "retriever.get_relevant_documents(\"Has Greta Gerwig directed any movies about women\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example specifies a composite filter\n", + "retriever.get_relevant_documents(\n", + " \"What's a highly rated (above 8.5), science fiction movie ?\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example specifies a query and composite filter\n", + "retriever.get_relevant_documents(\n", + " \"What's a movie about toys after 1990 but before 2005, and is animated\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filter k\n", + "\n", + "We can also use the self query retriever to specify `k`: the number of documents to fetch.\n", + "\n", + "We can do this by passing `enable_limit=True` to the constructor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "retriever = SelfQueryRetriever.from_llm(\n", + " llm,\n", + " vectorstore,\n", + " document_content_description,\n", + " metadata_field_info,\n", + " verbose=True,\n", + " enable_limit=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example only specifies a relevant query\n", + "retriever.get_relevant_documents(\"What are two movies about dinosaurs?\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Cleanup\n", + "\n", + "If you want to completely delete the collection from your Astra DB instance, run this.\n", + "\n", + "_(You will lose the data you stored in it.)_" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "vectorstore.delete_collection()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 62a2e9ee1967106d383d7a35e54b4488f736d177 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:17:38 -0800 Subject: [PATCH 040/215] langchain[patch]: Release 0.1.1 (#16103) --- libs/langchain/_test_minimum_requirements.txt | 2 +- libs/langchain/poetry.lock | 87 ++++++++++++++++--- libs/langchain/pyproject.toml | 4 +- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/libs/langchain/_test_minimum_requirements.txt b/libs/langchain/_test_minimum_requirements.txt index aed919a615010..2fb15b6524b35 100644 --- a/libs/langchain/_test_minimum_requirements.txt +++ b/libs/langchain/_test_minimum_requirements.txt @@ -1,2 +1,2 @@ langchain-core==0.1.7 -langchain-community==0.0.9 +langchain-community==0.0.13 diff --git a/libs/langchain/poetry.lock b/libs/langchain/poetry.lock index 404601a5c164c..a3fddd2f44107 100644 --- a/libs/langchain/poetry.lock +++ b/libs/langchain/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiodns" @@ -2358,7 +2358,7 @@ files = [ {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:c3692ecf3fe754c8c0f2c95ff19626584459eab110eaab66413b1e7425cd84e9"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, @@ -2368,7 +2368,6 @@ files = [ {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, - {file = "greenlet-3.0.0-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:553d6fb2324e7f4f0899e5ad2c427a4579ed4873f42124beba763f16032959af"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, @@ -3448,7 +3447,7 @@ files = [ [[package]] name = "langchain-community" -version = "0.0.9" +version = "0.0.13" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.8.1,<4.0" @@ -3458,7 +3457,7 @@ develop = true [package.dependencies] aiohttp = "^3.8.3" dataclasses-json = ">= 0.5.7, < 0.7" -langchain-core = ">=0.1.7,<0.2" +langchain-core = ">=0.1.9,<0.2" langsmith = "~0.0.63" numpy = "^1" PyYAML = ">=5.3" @@ -3476,7 +3475,7 @@ url = "../community" [[package]] name = "langchain-core" -version = "0.1.7" +version = "0.1.11" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -3745,6 +3744,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -6314,6 +6323,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -6321,8 +6331,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -6339,6 +6356,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -6346,6 +6364,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -7553,6 +7572,54 @@ description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, + {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, ] @@ -7562,7 +7629,7 @@ typing-extensions = ">=4.2.0" [package.extras] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +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)"] @@ -7572,7 +7639,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=7)"] +oracle = ["cx-oracle (>=7)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -7582,7 +7649,7 @@ postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] +sqlcipher = ["sqlcipher3-binary"] [[package]] name = "sqlite-vss" @@ -9061,4 +9128,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "00113cc914ec1dd07b5cb99d13fe9cb99ce79743743f80b345f745398faa3515" +content-hash = "2bc2d6bec75e54bc4dc09a5db31c05eef3c2756ee3ddedccdb6165becaa505be" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index 87a0080384b4c..661d28802bd44 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain" -version = "0.1.0" +version = "0.1.1" description = "Building applications with LLMs through composability" authors = [] license = "MIT" @@ -13,7 +13,7 @@ langchain-server = "langchain.server:main" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" langchain-core = ">=0.1.7,<0.2" -langchain-community = ">=0.0.9,<0.1" +langchain-community = ">=0.0.13,<0.1" pydantic = ">=1,<3" SQLAlchemy = ">=1.4,<3" requests = "^2" From 3d34347a85d32e8a1dd4064e84c99a0d58ea3572 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:39:07 -0800 Subject: [PATCH 041/215] langchain[patch]: bump core dep to 0.1.9 (#16104) --- libs/langchain/_test_minimum_requirements.txt | 2 +- libs/langchain/poetry.lock | 2 +- libs/langchain/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/langchain/_test_minimum_requirements.txt b/libs/langchain/_test_minimum_requirements.txt index 2fb15b6524b35..a370010215cb0 100644 --- a/libs/langchain/_test_minimum_requirements.txt +++ b/libs/langchain/_test_minimum_requirements.txt @@ -1,2 +1,2 @@ -langchain-core==0.1.7 +langchain-core==0.1.9 langchain-community==0.0.13 diff --git a/libs/langchain/poetry.lock b/libs/langchain/poetry.lock index a3fddd2f44107..0840dab296c4b 100644 --- a/libs/langchain/poetry.lock +++ b/libs/langchain/poetry.lock @@ -9128,4 +9128,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "2bc2d6bec75e54bc4dc09a5db31c05eef3c2756ee3ddedccdb6165becaa505be" +content-hash = "56656496974df81ce802cecd9a710bcf3bf9807b3496da4cea33a9a6e4146e86" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index 661d28802bd44..4fc8a74d3c012 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -12,7 +12,7 @@ langchain-server = "langchain.server:main" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" -langchain-core = ">=0.1.7,<0.2" +langchain-core = ">=0.1.9,<0.2" langchain-community = ">=0.0.13,<0.1" pydantic = ">=1,<3" SQLAlchemy = ">=1.4,<3" From 8840a8cc95179dd945b1f4ab9eb08e34318ed2f4 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:41:14 -0800 Subject: [PATCH 042/215] docs: tool-use use case (#15783) Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- docs/docs/use_cases/tool_use/agents.ipynb | 285 ++++++++++ .../tool_use/human_in_the_loop.ipynb | 274 +++++++++ docs/docs/use_cases/tool_use/index.ipynb | 53 ++ .../use_cases/tool_use/multiple_tools.ipynb | 259 +++++++++ docs/docs/use_cases/tool_use/parallel.ipynb | 197 +++++++ docs/docs/use_cases/tool_use/prompting.ipynb | 415 ++++++++++++++ docs/docs/use_cases/tool_use/quickstart.ipynb | 531 ++++++++++++++++++ .../tool_use/tool_error_handling.ipynb | 426 ++++++++++++++ .../langchain/chains/openai_functions/base.py | 11 +- .../langchain/output_parsers/openai_tools.py | 50 +- poetry.lock | 317 ++++++++++- pyproject.toml | 2 + 12 files changed, 2791 insertions(+), 29 deletions(-) create mode 100644 docs/docs/use_cases/tool_use/agents.ipynb create mode 100644 docs/docs/use_cases/tool_use/human_in_the_loop.ipynb create mode 100644 docs/docs/use_cases/tool_use/index.ipynb create mode 100644 docs/docs/use_cases/tool_use/multiple_tools.ipynb create mode 100644 docs/docs/use_cases/tool_use/parallel.ipynb create mode 100644 docs/docs/use_cases/tool_use/prompting.ipynb create mode 100644 docs/docs/use_cases/tool_use/quickstart.ipynb create mode 100644 docs/docs/use_cases/tool_use/tool_error_handling.ipynb diff --git a/docs/docs/use_cases/tool_use/agents.ipynb b/docs/docs/use_cases/tool_use/agents.ipynb new file mode 100644 index 0000000000000..f542e465a3652 --- /dev/null +++ b/docs/docs/use_cases/tool_use/agents.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "7b68af90-bfab-4407-93b6-d084cf948b4b", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 1\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "1925a807-fa01-44bc-8a03-d9907311c7f9", + "metadata": {}, + "source": [ + "## Agents\n", + "\n", + "Chains are great when we know the specific sequence of tool usage needed for any user input. But for certain use cases, how many times we use tools depends on the input. In these cases, we want to let the model itself decide how many times to use tools and in what order. [Agents](/docs/modules/agents/) let us do just this.\n", + "\n", + "LangChain comes with a number of built-in agents that are optimized for different use cases. Read about all the [agent types here](/docs/modules/agents/agent_types/).\n", + "\n", + "As an example, let's try out the OpenAI tools agent, which makes use of the new OpenAI tool-calling API (this is only available in the latest OpenAI models, and differs from function-calling in that the model can return multiple function invocations at once):" + ] + }, + { + "cell_type": "markdown", + "id": "c224a321-2f5a-410c-b466-a10d0199bad8", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6303995-a8f7-4504-8b29-e227683f375e", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "a33915ce-00c5-4379-8a83-c0053e471cdb", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54667a49-c226-486d-a887-33120c90cc91", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "aaaad3ad-085b-494e-84aa-9cb3e983c80b", + "metadata": {}, + "source": [ + "## Create tools\n", + "\n", + "First, we need to create some tool to call. For this example, we will create custom tools from functions. For more information on creating custom tools, please see [this guide](/docs/modules/agents/tools/)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1c44ba79-6ab2-4d55-8247-82fca4d9b70c", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int\n", + "\n", + "\n", + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent\n", + "\n", + "\n", + "tools = [multiply, add, exponentiate]" + ] + }, + { + "cell_type": "markdown", + "id": "a3d0c8ca-72bd-4187-b1e6-f5eef92eeb52", + "metadata": {}, + "source": [ + "## Create prompt" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e27a4e1a-938b-4b60-8e32-25e4ee530274", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_openai_tools_agent\n", + "from langchain_openai import ChatOpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bcc9536e-0328-4e29-9d3d-133f3e63e589", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m System Message \u001b[0m================================\n", + "\n", + "You are a helpful assistant\n", + "\n", + "=============================\u001b[1m Messages Placeholder \u001b[0m=============================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{chat_history}\u001b[0m\n", + "\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{input}\u001b[0m\n", + "\n", + "=============================\u001b[1m Messages Placeholder \u001b[0m=============================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{agent_scratchpad}\u001b[0m\n" + ] + } + ], + "source": [ + "# Get the prompt to use - you can modify this!\n", + "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", + "prompt.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "85e9875a-d8d4-4712-b3f0-b513c684451b", + "metadata": {}, + "source": [ + "## Create agent" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a1c5319d-6609-449d-8dd0-127e9a600656", + "metadata": {}, + "outputs": [], + "source": [ + "# Choose the LLM that will drive the agent\n", + "# Only certain models support this\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", temperature=0)\n", + "\n", + "# Construct the OpenAI Tools agent\n", + "agent = create_openai_tools_agent(model, tools, prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c86bfe50-c5b3-49ed-86c8-1fe8dcd0c83a", + "metadata": {}, + "outputs": [], + "source": [ + "# Create an agent executor by passing in the agent and tools\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)" + ] + }, + { + "cell_type": "markdown", + "id": "448d5ef2-9820-44d0-96d3-ff1d648e4b01", + "metadata": {}, + "source": [ + "## Invoke agent" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c098f8df-fd7f-4c13-963a-8e34194d3f84", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `exponentiate` with `{'base': 3, 'exponent': 5}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3m243\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `add` with `{'first_int': 12, 'second_int': 3}`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m15\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `multiply` with `{'first_int': 243, 'second_int': 15}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m3645\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `exponentiate` with `{'base': 3645, 'exponent': 2}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3m13286025\u001b[0m\u001b[32;1m\u001b[1;3mThe result of raising 3 to the fifth power and multiplying that by the sum of twelve and three, then squaring the whole result is 13,286,025.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result',\n", + " 'output': 'The result of raising 3 to the fifth power and multiplying that by the sum of twelve and three, then squaring the whole result is 13,286,025.'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result\"\n", + " }\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/human_in_the_loop.ipynb b/docs/docs/use_cases/tool_use/human_in_the_loop.ipynb new file mode 100644 index 0000000000000..137b3f5310406 --- /dev/null +++ b/docs/docs/use_cases/tool_use/human_in_the_loop.ipynb @@ -0,0 +1,274 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b09b745d-f006-4ecc-8772-afa266c43605", + "metadata": {}, + "source": [ + "# Human-in-the-loop\n", + "\n", + "There are certain tools that we don't trust a model to execute on its own. One thing we can do in such situations is require human approval before the tool is invoked." + ] + }, + { + "cell_type": "markdown", + "id": "09178c30-a633-4d7b-88ea-092316f14b6f", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e44bec05-9aa4-47b1-a660-c0a183533598", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "f09629b6-7f62-4879-a791-464739ca6b6b", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2bed0ccf-20cc-4fd3-9947-55471dd8c4da", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below:\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "43721981-4595-4721-bea0-5c67696426d3", + "metadata": {}, + "source": [ + "## Chain\n", + "\n", + "Suppose we have the following (dummy) tools and tool-calling chain:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "0221fdfd-2a18-4449-a123-e6b0b15bb3d9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'count_emails', 'args': {'last_n_days': 5}, 'output': 10}]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain.output_parsers import JsonOutputToolsParser\n", + "from langchain_community.tools.convert_to_openai import format_tool_to_openai_tool\n", + "from langchain_core.runnables import Runnable, RunnableLambda, RunnablePassthrough\n", + "from langchain_core.tools import tool\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "\n", + "@tool\n", + "def count_emails(last_n_days: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return last_n_days * 2\n", + "\n", + "\n", + "@tool\n", + "def send_email(message: str, recipient: str) -> str:\n", + " \"Add two integers.\"\n", + " return f\"Successfully sent email to {recipient}.\"\n", + "\n", + "\n", + "tools = [count_emails, send_email]\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0).bind(\n", + " tools=[format_tool_to_openai_tool(t) for t in tools]\n", + ")\n", + "\n", + "\n", + "def call_tool(tool_invocation: dict) -> Runnable:\n", + " \"\"\"Function for dynamically constructing the end of the chain based on the model-selected tool.\"\"\"\n", + " tool_map = {tool.name: tool for tool in tools}\n", + " tool = tool_map[tool_invocation[\"type\"]]\n", + " return RunnablePassthrough.assign(output=itemgetter(\"args\") | tool)\n", + "\n", + "\n", + "# .map() allows us to apply a function to a list of inputs.\n", + "call_tool_list = RunnableLambda(call_tool).map()\n", + "chain = model | JsonOutputToolsParser() | call_tool_list\n", + "chain.invoke(\"how many emails did i get in the last 5 days?\")" + ] + }, + { + "cell_type": "markdown", + "id": "258c1c7b-a765-4558-93fe-d0defbc29223", + "metadata": {}, + "source": [ + "## Adding human approval\n", + "\n", + "We can add a simple human approval step to our tool_chain function:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "341fb055-0315-47bc-8f72-ed6103d2981f", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "\n", + "def human_approval(tool_invocations: list) -> Runnable:\n", + " tool_strs = \"\\n\\n\".join(\n", + " json.dumps(tool_call, indent=2) for tool_call in tool_invocations\n", + " )\n", + " msg = (\n", + " f\"Do you approve of the following tool invocations\\n\\n{tool_strs}\\n\\n\"\n", + " \"Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no.\"\n", + " )\n", + " resp = input(msg)\n", + " if resp.lower() not in (\"yes\", \"y\"):\n", + " raise ValueError(f\"Tool invocations not approved:\\n\\n{tool_strs}\")\n", + " return tool_invocations" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "25dca07b-56ca-4b94-9955-d4f3e9895e03", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Do you approve of the following tool invocations\n", + "\n", + "{\n", + " \"type\": \"count_emails\",\n", + " \"args\": {\n", + " \"last_n_days\": 5\n", + " }\n", + "}\n", + "\n", + "Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no. y\n" + ] + }, + { + "data": { + "text/plain": [ + "[{'type': 'count_emails', 'args': {'last_n_days': 5}, 'output': 10}]" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = model | JsonOutputToolsParser() | human_approval | call_tool_list\n", + "chain.invoke(\"how many emails did i get in the last 5 days?\")" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "f558f2cd-847b-4ef9-a770-3961082b540c", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Do you approve of the following tool invocations\n", + "\n", + "{\n", + " \"type\": \"send_email\",\n", + " \"args\": {\n", + " \"message\": \"What's up homie\",\n", + " \"recipient\": \"sally@gmail.com\"\n", + " }\n", + "}\n", + "\n", + "Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no. no\n" + ] + }, + { + "ename": "ValueError", + "evalue": "Tool invocations not approved:\n\n{\n \"type\": \"send_email\",\n \"args\": {\n \"message\": \"What's up homie\",\n \"recipient\": \"sally@gmail.com\"\n }\n}", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[32], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mchain\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSend sally@gmail.com an email saying \u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mWhat\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43ms up homie\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:1774\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 1772\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1773\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 1774\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1775\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1776\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 1777\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1778\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1779\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1780\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1781\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 1782\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:3074\u001b[0m, in \u001b[0;36mRunnableLambda.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 3072\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Invoke this runnable synchronously.\"\"\"\u001b[39;00m\n\u001b[1;32m 3073\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfunc\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m-> 3074\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3075\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_invoke\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3076\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3077\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_config\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3078\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3079\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3080\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 3081\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 3082\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCannot invoke a coroutine function synchronously.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 3083\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUse `ainvoke` instead.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 3084\u001b[0m )\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:975\u001b[0m, in \u001b[0;36mRunnable._call_with_config\u001b[0;34m(self, func, input, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 971\u001b[0m context \u001b[38;5;241m=\u001b[39m copy_context()\n\u001b[1;32m 972\u001b[0m context\u001b[38;5;241m.\u001b[39mrun(var_child_runnable_config\u001b[38;5;241m.\u001b[39mset, child_config)\n\u001b[1;32m 973\u001b[0m output \u001b[38;5;241m=\u001b[39m cast(\n\u001b[1;32m 974\u001b[0m Output,\n\u001b[0;32m--> 975\u001b[0m \u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 976\u001b[0m \u001b[43m \u001b[49m\u001b[43mcall_func_with_variable_args\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 977\u001b[0m \u001b[43m \u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[1;32m 978\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[1;32m 979\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 980\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 981\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 982\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m,\n\u001b[1;32m 983\u001b[0m )\n\u001b[1;32m 984\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 985\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/config.py:323\u001b[0m, in \u001b[0;36mcall_func_with_variable_args\u001b[0;34m(func, input, config, run_manager, **kwargs)\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m accepts_run_manager(func):\n\u001b[1;32m 322\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_manager\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m run_manager\n\u001b[0;32m--> 323\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:2950\u001b[0m, in \u001b[0;36mRunnableLambda._invoke\u001b[0;34m(self, input, run_manager, config, **kwargs)\u001b[0m\n\u001b[1;32m 2948\u001b[0m output \u001b[38;5;241m=\u001b[39m chunk\n\u001b[1;32m 2949\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 2950\u001b[0m output \u001b[38;5;241m=\u001b[39m \u001b[43mcall_func_with_variable_args\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2951\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\n\u001b[1;32m 2952\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2953\u001b[0m \u001b[38;5;66;03m# If the output is a runnable, invoke it\u001b[39;00m\n\u001b[1;32m 2954\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(output, Runnable):\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/config.py:323\u001b[0m, in \u001b[0;36mcall_func_with_variable_args\u001b[0;34m(func, input, config, run_manager, **kwargs)\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m accepts_run_manager(func):\n\u001b[1;32m 322\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_manager\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m run_manager\n\u001b[0;32m--> 323\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[30], line 11\u001b[0m, in \u001b[0;36mhuman_approval\u001b[0;34m(tool_invocations)\u001b[0m\n\u001b[1;32m 9\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[38;5;28minput\u001b[39m(msg)\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m resp\u001b[38;5;241m.\u001b[39mlower() \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myes\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124my\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m---> 11\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTool invocations not approved:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mtool_strs\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m tool_invocations\n", + "\u001b[0;31mValueError\u001b[0m: Tool invocations not approved:\n\n{\n \"type\": \"send_email\",\n \"args\": {\n \"message\": \"What's up homie\",\n \"recipient\": \"sally@gmail.com\"\n }\n}" + ] + } + ], + "source": [ + "chain.invoke(\"Send sally@gmail.com an email saying 'What's up homie'\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e938d8f1-df93-4726-a465-78e596312246", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/index.ipynb b/docs/docs/use_cases/tool_use/index.ipynb new file mode 100644 index 0000000000000..0ca033e3c1925 --- /dev/null +++ b/docs/docs/use_cases/tool_use/index.ipynb @@ -0,0 +1,53 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "451cda29-bed0-4558-9ed7-099bdd12ad60", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 2\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "14b94240", + "metadata": {}, + "source": [ + "# Tool use\n", + "\n", + "An exciting use case for LLMs is building natural language interfaces for other \"tools\", whether those are APIs, functions, databases, etc. LangChain is great for building such interfaces because it has:\n", + "\n", + "- Good model output parsing, which makes it easy to extract JSON, XML, OpenAI function-calls, etc. from model outputs.\n", + "- A large collection of built-in [Tools](/docs/integrations/tools).\n", + "- Provides a lot of flexibility in how you call these tools.\n", + "\n", + "There are two main ways to use tools: [chains](/docs/modules/chains) and [agents](/docs/modules/agents/). Chains lets you create a pre-defined sequence of tool usage(s). Agents let the model use tools in a loop, so that it can decide how many times to use tools.\n", + "\n", + "To get started with both approaches, head to the [Quickstart](/docs/use_cases/tool_use/quickstart) page." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/multiple_tools.ipynb b/docs/docs/use_cases/tool_use/multiple_tools.ipynb new file mode 100644 index 0000000000000..fd520a951a5de --- /dev/null +++ b/docs/docs/use_cases/tool_use/multiple_tools.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "1ea1fe24-fe1e-463b-a52c-79f0ef02328e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 2\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "95982bf1-7d9d-4dd6-a4ad-9de0719fe17f", + "metadata": {}, + "source": [ + "# Choosing between multiple tools\n", + "\n", + "In our [Quickstart](/docs/use_cases/tool_use/quickstart) we went over how to build a Chain that calls a single `multiply` tool. Now let's take a look at how we might augment this chain so that it can pick from a number of tools to call. We'll focus on Chains since [Agents](/docs/use_cases/tool_use/agents) can route between multiple tools by default." + ] + }, + { + "cell_type": "markdown", + "id": "3fafec38-443a-42ad-a913-5be7667e3734", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78411bf1-0117-4f33-a3d7-f3d77a97bb78", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "59d08fd0-ddd9-4c74-bcea-a5ca3a86e542", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4185e74b-0500-4cad-ace0-bac37de466ac", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "d28159f5-b7d0-4385-aa44-4cd1b64507bb", + "metadata": {}, + "source": [ + "## Tools\n", + "\n", + "Recall we already had a `multiply` tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e13ec98c-8521-4d63-b521-caf92da87b70", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int" + ] + }, + { + "cell_type": "markdown", + "id": "3de233af-b3bd-4f0c-8b1a-83527143a8db", + "metadata": {}, + "source": [ + "And now we can add to it a `exponentiate` and `add` tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e93661cd-a2ba-4ada-91ad-baf1b60879ec", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent" + ] + }, + { + "cell_type": "markdown", + "id": "bbea4555-ed10-4a18-b802-e9a3071f132b", + "metadata": {}, + "source": [ + "The main difference between using one Tool and many, is that in the case of many we can't be sure which Tool the model will invoke. So we cannot hardcode, like we did in the [Quickstart](/docs/use_cases/tool_use/quickstart), a specific tool into our chain. Instead we'll add `call_tool_list`, a `RunnableLambda` that takes the `JsonOutputToolsParser` output and actually builds the end of the chain based on it, meaning it appends the Tools that were envoked to the end of the chain at runtime. We can do this because LCEL has the cool property that in any Runnable (the core building block of LCEL) sequence, if one component returns more Runnables, those are run as part of the chain." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c35359ae-a740-48c5-b5e7-1a377fb25aa2", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "from typing import Union\n", + "\n", + "from langchain.output_parsers import JsonOutputToolsParser\n", + "from langchain_community.tools.convert_to_openai import (\n", + " format_tool_to_openai_tool,\n", + ")\n", + "from langchain_core.runnables import (\n", + " Runnable,\n", + " RunnableLambda,\n", + " RunnableMap,\n", + " RunnablePassthrough,\n", + ")\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo\")\n", + "tools = [multiply, exponentiate, add]\n", + "model_with_tools = model.bind(tools=[format_tool_to_openai_tool(t) for t in tools])\n", + "tool_map = {tool.name: tool for tool in tools}\n", + "\n", + "\n", + "def call_tool(tool_invocation: dict) -> Union[str, Runnable]:\n", + " \"\"\"Function for dynamically constructing the end of the chain based on the model-selected tool.\"\"\"\n", + " tool = tool_map[tool_invocation[\"type\"]]\n", + " return RunnablePassthrough.assign(output=itemgetter(\"args\") | tool)\n", + "\n", + "\n", + "# .map() allows us to apply a function to a list of inputs.\n", + "call_tool_list = RunnableLambda(call_tool).map()\n", + "chain = model_with_tools | JsonOutputToolsParser() | call_tool_list" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ea6dbb32-ec9b-4c70-a90f-a2db93978cf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'multiply',\n", + " 'args': {'first_int': 23, 'second_int': 7},\n", + " 'output': 161}]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\"What's 23 times 7\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b1c6c0f8-6d04-40d4-a40e-8719ca7b27c2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'add',\n", + " 'args': {'first_int': 1000000, 'second_int': 1000000000},\n", + " 'output': 1001000000}]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\"add a million plus a billion\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "ce76f299-1a4d-421c-afa4-a6346e34285c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'exponentiate',\n", + " 'args': {'base': 37, 'exponent': 3},\n", + " 'output': 50653}]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\"cube thirty-seven\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/parallel.ipynb b/docs/docs/use_cases/tool_use/parallel.ipynb new file mode 100644 index 0000000000000..241e1582c3d95 --- /dev/null +++ b/docs/docs/use_cases/tool_use/parallel.ipynb @@ -0,0 +1,197 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "95982bf1-7d9d-4dd6-a4ad-9de0719fe17f", + "metadata": {}, + "source": [ + "# Chains with parallel tool use\n", + "\n", + "In the [Chains with multiple tools](/docs/use_cases/tool_use/multiple_tools) guide we saw how to build function-calling chains that select between multiple tools. Some models, like the OpenAI models released in Fall 2023, also support parallel function calling, which allows you to invoke multiple functions (or the same function multiple times) in a single model call. Our previous chain from the multiple tools guides actually already supports this, we just need to use an OpenAI model capable of parallel function calling." + ] + }, + { + "cell_type": "markdown", + "id": "3fafec38-443a-42ad-a913-5be7667e3734", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78411bf1-0117-4f33-a3d7-f3d77a97bb78", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "59d08fd0-ddd9-4c74-bcea-a5ca3a86e542", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4185e74b-0500-4cad-ace0-bac37de466ac", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "d28159f5-b7d0-4385-aa44-4cd1b64507bb", + "metadata": {}, + "source": [ + "## Tools" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e13ec98c-8521-4d63-b521-caf92da87b70", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int\n", + "\n", + "\n", + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent" + ] + }, + { + "cell_type": "markdown", + "id": "119d419c-1c61-4e0d-834a-5dabb72f5514", + "metadata": {}, + "source": [ + "# Chain\n", + "\n", + "Notice we use an `-1106` model, which as of this writing is the only kind that supports parallel function calling:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c35359ae-a740-48c5-b5e7-1a377fb25aa2", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "from typing import Union\n", + "\n", + "from langchain.output_parsers import JsonOutputToolsParser\n", + "from langchain_community.tools.convert_to_openai import (\n", + " format_tool_to_openai_tool,\n", + ")\n", + "from langchain_core.runnables import (\n", + " Runnable,\n", + " RunnableLambda,\n", + " RunnableMap,\n", + " RunnablePassthrough,\n", + ")\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo-1106\")\n", + "tools = [multiply, exponentiate, add]\n", + "model_with_tools = model.bind(tools=[format_tool_to_openai_tool(t) for t in tools])\n", + "tool_map = {tool.name: tool for tool in tools}\n", + "\n", + "\n", + "def call_tool(tool_invocation: dict) -> Union[str, Runnable]:\n", + " \"\"\"Function for dynamically constructing the end of the chain based on the model-selected tool.\"\"\"\n", + " tool = tool_map[tool_invocation[\"type\"]]\n", + " return RunnablePassthrough.assign(output=itemgetter(\"args\") | tool)\n", + "\n", + "\n", + "# .map() allows us to apply a function to a list of inputs.\n", + "call_tool_list = RunnableLambda(call_tool).map()\n", + "chain = model_with_tools | JsonOutputToolsParser() | call_tool_list" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ea6dbb32-ec9b-4c70-a90f-a2db93978cf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'multiply',\n", + " 'args': {'first_int': 23, 'second_int': 7},\n", + " 'output': 161},\n", + " {'type': 'add', 'args': {'first_int': 5, 'second_int': 18}, 'output': 23},\n", + " {'type': 'add',\n", + " 'args': {'first_int': 1000000, 'second_int': 1000000000},\n", + " 'output': 1001000000},\n", + " {'type': 'exponentiate',\n", + " 'args': {'base': 37, 'exponent': 3},\n", + " 'output': 50653}]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\n", + " \"What's 23 times 7, and what's five times 18 and add a million plus a billion and cube thirty-seven\"\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/prompting.ipynb b/docs/docs/use_cases/tool_use/prompting.ipynb new file mode 100644 index 0000000000000..2b30bf2ec6491 --- /dev/null +++ b/docs/docs/use_cases/tool_use/prompting.ipynb @@ -0,0 +1,415 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "3243cb05-8243-421f-99fa-98201abb3094", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "14b94240", + "metadata": {}, + "source": [ + "# Prompting for tool use\n", + "\n", + "In this guide we'll build a Chain that does not rely on any special model APIs (like function-calling, which we showed in the [Quickstart](/docs/use_cases/tool_use/quickstart)) and instead just prompts the model directly to invoke tools." + ] + }, + { + "cell_type": "markdown", + "id": "a0a22cb8-19e7-450a-9d1b-6848d2c81cd1", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c556c5e-b785-428b-8e7d-efd34a2a1adb", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "5e727d22-f861-4eee-882a-688f8efc885e", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "527ef906-0104-4872-b4e5-f371cf73feba", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below:\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "68946881", + "metadata": {}, + "source": [ + "## Create a tool\n", + "\n", + "First, we need to create a tool to call. For this example, we will create a custom tool from a function. For more information on all details related to creating custom tools, please see [this guide](/docs/modules/agents/tools/)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "90187d07", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d7009e1a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multiply\n", + "multiply(first_int: int, second_int: int) -> int - Multiply two integers together.\n", + "{'first_int': {'title': 'First Int', 'type': 'integer'}, 'second_int': {'title': 'Second Int', 'type': 'integer'}}\n" + ] + } + ], + "source": [ + "print(multiply.name)\n", + "print(multiply.description)\n", + "print(multiply.args)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "be77e780", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiply.invoke({\"first_int\": 4, \"second_int\": 5})" + ] + }, + { + "cell_type": "markdown", + "id": "15dd690e-e54d-4209-91a4-181f69a452ac", + "metadata": {}, + "source": [ + "## Creating our prompt\n", + "\n", + "We'll want to write a prompt that specifies the tools the model has access to, the arguments to those tools, and the desired output format of the model. In this case we'll instruct it to output a JSON blob of the form `{\"name\": \"...\", \"arguments\": {...}}`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c64818f0-9364-423c-922e-bdfb8f01e726", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'multiply: multiply(first_int: int, second_int: int) -> int - Multiply two integers together.'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.tools.render import render_text_description\n", + "\n", + "rendered_tools = render_text_description([multiply])\n", + "rendered_tools" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "63552d4d-8bd6-4aca-8805-56e236f6552d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "system_prompt = f\"\"\"You are an assistant that has access to the following set of tools. Here are the names and descriptions for each tool:\n", + "\n", + "{rendered_tools}\n", + "\n", + "Given the user input, return the name and input of the tool to use. Return your response as a JSON blob with 'name' and 'arguments' keys.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system_prompt), (\"user\", \"{input}\")]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "14df2cd5-b6fa-4b10-892d-e8692c7931e5", + "metadata": {}, + "source": [ + "## Adding an output parser\n", + "\n", + "We'll use the `JsonOutputParser` for parsing our models output to JSON." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f129f5bd-127c-4c95-8f34-8f437da7ca8f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'multiply', 'arguments': {'first_int': 13, 'second_int': 4}}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.output_parsers import JsonOutputParser\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "chain = prompt | model | JsonOutputParser()\n", + "chain.invoke({\"input\": \"what's thirteen times 4\"})" + ] + }, + { + "cell_type": "markdown", + "id": "8e29dd4c-8eb5-457f-92d1-8add076404dc", + "metadata": {}, + "source": [ + "## Invoking the tool\n", + "\n", + "We can invoke the tool as part of the chain by passing along the model-generated \"arguments\" to it:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0555b384-fde6-4404-86e0-7ea199003d58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "52" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "chain = prompt | model | JsonOutputParser() | itemgetter(\"arguments\") | multiply\n", + "chain.invoke({\"input\": \"what's thirteen times 4\"})" + ] + }, + { + "cell_type": "markdown", + "id": "8d60b2cb-6ce0-48fc-8d18-d2337161a53d", + "metadata": {}, + "source": [ + "## Choosing from multiple tools\n", + "\n", + "Suppose we have multiple tools we want the chain to be able to choose from:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "95c86d32-ee45-4c87-a28c-14eff19b49e9", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent" + ] + }, + { + "cell_type": "markdown", + "id": "748405ff-4c85-4bd7-82e1-30458b5a4106", + "metadata": {}, + "source": [ + "With function calling, we can do this like so:" + ] + }, + { + "cell_type": "markdown", + "id": "eb3aa89e-40e1-45ec-b1f3-ab28cfc8e42d", + "metadata": {}, + "source": [ + "If we want to run the model selected tool, we can do so using a function that returns the tool based on the model output. Specifically, our function will action return it's own subchain that gets the \"arguments\" part of the model output and passes it to the chosen tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "db254773-5b8e-43d0-aabe-c21566c154cd", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [add, exponentiate, multiply]\n", + "\n", + "\n", + "def tool_chain(model_output):\n", + " tool_map = {tool.name: tool for tool in tools}\n", + " chosen_tool = tool_map[model_output[\"name\"]]\n", + " return itemgetter(\"arguments\") | chosen_tool" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ad9f5cff-b86a-45fc-9ce4-b0aa9025a378", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1135" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rendered_tools = render_text_description(tools)\n", + "system_prompt = f\"\"\"You are an assistant that has access to the following set of tools. Here are the names and descriptions for each tool:\n", + "\n", + "{rendered_tools}\n", + "\n", + "Given the user input, return the name and input of the tool to use. Return your response as a JSON blob with 'name' and 'arguments' keys.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system_prompt), (\"user\", \"{input}\")]\n", + ")\n", + "\n", + "chain = prompt | model | JsonOutputParser() | tool_chain\n", + "chain.invoke({\"input\": \"what's 3 plus 1132\"})" + ] + }, + { + "cell_type": "markdown", + "id": "b4a9c5aa-f60a-4017-af6f-1ff6e04bfb61", + "metadata": {}, + "source": [ + "## Returning tool inputs\n", + "\n", + "It can be helpful to return not only tool outputs but also tool inputs. We can easily do this with LCEL by `RunnablePassthrough.assign`-ing the tool output. This will take whatever the input is to the RunnablePassrthrough components (assumed to be a dictionary) and add a key to it while still passing through everything that's currently in the input:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "45404406-859d-4caa-8b9d-5838162c80a0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'add',\n", + " 'arguments': {'first_int': 3, 'second_int': 1132},\n", + " 'output': 1135}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "chain = (\n", + " prompt | model | JsonOutputParser() | RunnablePassthrough.assign(output=tool_chain)\n", + ")\n", + "chain.invoke({\"input\": \"what's 3 plus 1132\"})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/quickstart.ipynb b/docs/docs/use_cases/tool_use/quickstart.ipynb new file mode 100644 index 0000000000000..3d31c5231bc64 --- /dev/null +++ b/docs/docs/use_cases/tool_use/quickstart.ipynb @@ -0,0 +1,531 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "500e8846-91c2-4716-9bd6-b9672c6daf78", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "14b94240", + "metadata": {}, + "source": [ + "# Quickstart\n", + "\n", + "In this guide, we will go over the basic ways to create Chains and Agents that call Tools. Tools can be just about anything — APIs, functions, databases, etc. Tools allow us to extend the capabilities of a model beyond just outputting text/messages. The key to using models with tools is correctly prompting a model and parsing its response so that it chooses the right ools and provides the right inputs for them." + ] + }, + { + "cell_type": "markdown", + "id": "e6b79a42-0349-42c6-9ce8-72220e838e8d", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2274266-755a-4e90-b257-5180fb089af2", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "36a9c6fc-8264-462f-b8d7-9c7bbec22ef9", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57a81b7a-4fd9-4f28-bc32-7b98b522e1b0", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "68946881", + "metadata": {}, + "source": [ + "## Create a tool\n", + "\n", + "First, we need to create a tool to call. For this example, we will create a custom tool from a function. For more information on creating custom tools, please see [this guide](/docs/modules/agents/tools/)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "90187d07", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d7009e1a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multiply\n", + "multiply(first_int: int, second_int: int) -> int - Multiply two integers together.\n", + "{'first_int': {'title': 'First Int', 'type': 'integer'}, 'second_int': {'title': 'Second Int', 'type': 'integer'}}\n" + ] + } + ], + "source": [ + "print(multiply.name)\n", + "print(multiply.description)\n", + "print(multiply.args)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "be77e780", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiply.invoke({\"first_int\": 4, \"second_int\": 5})" + ] + }, + { + "cell_type": "markdown", + "id": "19ba4d63", + "metadata": {}, + "source": [ + "## Chains\n", + "\n", + "If we know that we only need to use a tool a fixed number of times, we can create a chain for doing so. Let's create a simple chain that just multiplies user-specified numbers.\n", + "\n", + "### Function calling\n", + "One of the most reliable ways to use tools with LLMs is with function calling APIs (also sometimes called tool calling or parallel function calling). This only works with models that explicitly support function calling, like OpenAI models.\n", + "\n", + "First we'll define our model and tools. We'll start with just a single tool, `multiply`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9bce8935-1465-45ac-8a93-314222c753c4", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_openai.chat_models import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo-1106\")" + ] + }, + { + "cell_type": "markdown", + "id": "c22e6f0f-c5ad-4c0f-9514-e626704ea51c", + "metadata": {}, + "source": [ + "Next we'll convert our LangChain Tool to an OpenAI format JSONSchema function, and bind this as the `tools` argument to be passed to all ChatOpenAI calls. Since we only have a single Tool and in this initial chain we want to make sure it's always used, we'll also specify `tool_choice`. See the [OpenAI chat API reference](https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice) for more on these parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2babd759-bccd-4d50-95ad-365a07347926", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'function',\n", + " 'function': {'name': 'multiply',\n", + " 'description': 'multiply(first_int: int, second_int: int) -> int - Multiply two integers together.',\n", + " 'parameters': {'title': 'multiplySchemaSchema',\n", + " 'type': 'object',\n", + " 'properties': {'first_int': {'title': 'First Int', 'type': 'integer'},\n", + " 'second_int': {'title': 'Second Int', 'type': 'integer'}},\n", + " 'required': ['first_int', 'second_int']}}}]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.tools.convert_to_openai import (\n", + " format_tool_to_openai_tool,\n", + ")\n", + "\n", + "formatted_tools = [format_tool_to_openai_tool(multiply)]\n", + "formatted_tools" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3bfe2cdc-7d72-457c-a9a1-5fa1e0bcde55", + "metadata": {}, + "outputs": [], + "source": [ + "model_with_tools = model.bind(\n", + " tools=formatted_tools,\n", + " # We specify tool_choice to enforce that the 'multiply' function is called by the model.\n", + " tool_choice={\"type\": \"function\", \"function\": {\"name\": \"multiply\"}},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9fa2ba14-9a97-4960-a6c7-422edecdaf4b", + "metadata": {}, + "source": [ + "Now we'll compose out tool-calling model with a `JsonOutputToolsParser`, a built-in LangChain output parser that converts an OpenAI function-calling response to a list of `{\"type\": \"TOOL_NAME\", \"args\": {...}}` dicts with the tools to invoke and arguments to invoke them with." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5518aba4-c44d-4896-9b63-fc9d56c245df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'type': 'multiply', 'args': {'first_int': 4, 'second_int': 23}}]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.output_parsers import JsonOutputToolsParser\n", + "\n", + "chain = model_with_tools | JsonOutputToolsParser()\n", + "chain.invoke(\"What's four times 23\")" + ] + }, + { + "cell_type": "markdown", + "id": "7f712d8d-0314-4d3d-b563-378b72fd8bb5", + "metadata": {}, + "source": [ + "Since we know we're always invoking the `multiply` tool, we can simplify our output a bit to return only the args for the `multiply` tool using the `JsonoutputKeyToolsParser`. To further simplify we'll specify `return_single=True`, so that instead of a list of tool invocations our output parser returns only the first tool invocation." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cfacfcdc-8a45-4c60-a175-7efe9534f83e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'first_int': 4, 'second_int': 23}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.output_parsers import JsonOutputKeyToolsParser\n", + "\n", + "chain = model_with_tools | JsonOutputKeyToolsParser(\n", + " key_name=\"multiply\", return_single=True\n", + ")\n", + "chain.invoke(\"What's four times 23\")" + ] + }, + { + "cell_type": "markdown", + "id": "8ba1764d-0272-4f98-adcf-b48cb2c0a315", + "metadata": {}, + "source": [ + "### Invoking the tool\n", + "\n", + "Great! We're able to generate tool invocations. But what if we want to actually call the tool? To do that we just need to pass them to the tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4f5325ca-e5dc-4d1a-ba36-b085a029c90a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "92" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "# Note: the `.map()` at the end of `multiply` allows us to pass in a list of `multiply` arguments instead of a single one.\n", + "chain = (\n", + " model_with_tools\n", + " | JsonOutputKeyToolsParser(key_name=\"multiply\", return_single=True)\n", + " | multiply\n", + ")\n", + "chain.invoke(\"What's four times 23\")" + ] + }, + { + "cell_type": "markdown", + "id": "0521d3d5", + "metadata": {}, + "source": [ + "## Agents\n", + "\n", + "Chains are great when we know the specific sequence of tool usage needed for any user input. But for certain use cases, how many times we use tools depends on the input. In these cases, we want to let the model itself decide how many times to use tools and in what order. [Agents](/docs/modules/agents/) let us do just this.\n", + "\n", + "LangChain comes with a number of built-in agents that are optimized for different use cases. Read about all the [agent types here](/docs/modules/agents/agent_types/).\n", + "\n", + "As an example, let's try out the OpenAI tools agent, which makes use of the new OpenAI tool-calling API (this is only available in the latest OpenAI models, and differs from function-calling in that the model can return multiple function invocations at once):" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "21723cf4-9421-4a8d-92a6-eeeb8f4367f1", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_openai_tools_agent\n", + "from langchain_openai import ChatOpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "6be83879-9da3-4dd9-b147-a79f76affd7a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')),\n", + " MessagesPlaceholder(variable_name='chat_history', optional=True),\n", + " HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')),\n", + " MessagesPlaceholder(variable_name='agent_scratchpad')]" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get the prompt to use - you can modify this!\n", + "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", + "prompt.messages" + ] + }, + { + "cell_type": "markdown", + "id": "616f9714-5b18-4eed-b88a-d38e4cb1de99", + "metadata": {}, + "source": [ + "Agents are also great because they make it easy to use multiple tools. To learn how to build Chains that use multiple tools, check out the [Chains with multiple tools](/docs/use_cases/tool_use/multiple_tools) page." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "95c86d32-ee45-4c87-a28c-14eff19b49e9", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent\n", + "\n", + "\n", + "tools = [multiply, add, exponentiate]" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "17b09ac6-c9b7-4340-a8a0-3d3061f7888c", + "metadata": {}, + "outputs": [], + "source": [ + "# Choose the LLM that will drive the agent\n", + "# Only certain models support this\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", temperature=0)\n", + "\n", + "# Construct the OpenAI Tools agent\n", + "agent = create_openai_tools_agent(model, tools, prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "675091d2-cac9-45c4-a5d7-b760ee6c1986", + "metadata": {}, + "outputs": [], + "source": [ + "# Create an agent executor by passing in the agent and tools\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)" + ] + }, + { + "cell_type": "markdown", + "id": "a6099ab6-2fa6-452d-b73c-7fb65daab451", + "metadata": {}, + "source": [ + "With an agent, we can ask questions that require arbitrarily-many uses of our tools:" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "f7dbb240-809e-4e41-8f63-1a4636e8e26d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `exponentiate` with `{'base': 3, 'exponent': 5}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3m243\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `add` with `{'first_int': 12, 'second_int': 3}`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m15\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `multiply` with `{'first_int': 243, 'second_int': 15}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m3645\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `exponentiate` with `{'base': 3645, 'exponent': 2}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3m13286025\u001b[0m\u001b[32;1m\u001b[1;3mThe result of raising 3 to the fifth power and multiplying that by the sum of twelve and three, then squaring the whole result is 13,286,025.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result',\n", + " 'output': 'The result of raising 3 to the fifth power and multiplying that by the sum of twelve and three, then squaring the whole result is 13,286,025.'}" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b0e4b7f4-58ce-4ca0-a986-d05a436a7ccf", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Here we've gone over the basic ways to use Tools with Chains and Agents. We recommend the following sections to explore next:\n", + "\n", + "- [Agents](/docs/modules/agents/): Everything related to Agents.\n", + "- [Choosing between multiple tools](/docs/use_cases/tool_use/multiple_tools): How to make tool chains that select from multiple tools.\n", + "- [Prompting for tool use](/docs/use_cases/tool_use/prompting): How to make tool chains that prompt models directly, without using function-calling APIs.\n", + "- [Parallel tool use](/docs/use_cases/tool_use/parallel): How to make tool chains that invoke multiple tools at once." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/tool_use/tool_error_handling.ipynb b/docs/docs/use_cases/tool_use/tool_error_handling.ipynb new file mode 100644 index 0000000000000..81800a9fa495d --- /dev/null +++ b/docs/docs/use_cases/tool_use/tool_error_handling.ipynb @@ -0,0 +1,426 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5d60cbb9-2a6a-43ea-a9e9-f67b16ddd2b2", + "metadata": {}, + "source": [ + "# Tool error handling\n", + "\n", + "Using a model to invoke a tool has some obvious potential failure modes. Firstly, the model needs to return a output that can be parsed at all. Secondly, the model needs to return tool arguments that are valid.\n", + "\n", + "We can build error handling into our chains to mitigate these failure modes." + ] + }, + { + "cell_type": "markdown", + "id": "712c774f-27c7-4351-a196-39900ca155f5", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63056c24-9834-4e3d-8bc5-54b1e6c5df86", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "68107597-0c8c-4bb5-8c12-9992fabdf71a", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08785b6d-722d-4620-b6ec-36deb3842c69", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below:\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "0a50f93a-5d6f-4691-8f98-27239a1c2f95", + "metadata": {}, + "source": [ + "## Chain\n", + "\n", + "Suppose we have the following (dummy) tool and tool-calling chain. We'll make our tool intentionally convoluted to try and trip up the model." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1d20604e-c4d1-4d21-841b-23e4f61aec36", + "metadata": {}, + "outputs": [], + "source": [ + "# Define tool\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:\n", + " \"\"\"Do something complex with a complex tool.\"\"\"\n", + " return int_arg * float_arg" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "553c2c13-28c8-4451-8a3a-6c31d52dc31d", + "metadata": {}, + "outputs": [], + "source": [ + "# Define model and bind tool\n", + "from langchain_community.tools.convert_to_openai import format_tool_to_openai_tool\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "model_with_tools = model.bind(\n", + " tools=[format_tool_to_openai_tool(complex_tool)],\n", + " tool_choice={\"type\": \"function\", \"function\": {\"name\": \"complex_tool\"}},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "802b2eca-9f79-4d6c-8257-85139ca5c752", + "metadata": {}, + "outputs": [], + "source": [ + "# Define chain\n", + "from operator import itemgetter\n", + "\n", + "from langchain.output_parsers import JsonOutputKeyToolsParser\n", + "from langchain_core.runnables import Runnable, RunnableLambda, RunnablePassthrough\n", + "\n", + "chain = (\n", + " model_with_tools\n", + " | JsonOutputKeyToolsParser(key_name=\"complex_tool\", return_single=True)\n", + " | complex_tool\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c34f005e-63f0-4841-9461-ca36c36607fc", + "metadata": {}, + "source": [ + "We can see that when we try to invoke this chain with even a fairly explicit input, the model fails to correctly call the tool (it forgets the `dict_arg` argument)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d354664c-ac44-4967-a35f-8912b3ad9477", + "metadata": {}, + "outputs": [ + { + "ename": "ValidationError", + "evalue": "1 validation error for complex_toolSchemaSchema\ndict_arg\n field required (type=value_error.missing)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mchain\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43muse complex tool. the args are 5, 2.1, empty dictionary. don\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mt forget dict_arg\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m 3\u001b[0m \u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:1774\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 1772\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1773\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 1774\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1775\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1776\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 1777\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1778\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1779\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1780\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1781\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 1782\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:210\u001b[0m, in \u001b[0;36mBaseTool.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 204\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 205\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mstr\u001b[39m, Dict],\n\u001b[1;32m 206\u001b[0m config: Optional[RunnableConfig] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 207\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 208\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Any:\n\u001b[1;32m 209\u001b[0m config \u001b[38;5;241m=\u001b[39m ensure_config(config)\n\u001b[0;32m--> 210\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 211\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 212\u001b[0m \u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mcallbacks\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 213\u001b[0m \u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtags\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 214\u001b[0m \u001b[43m \u001b[49m\u001b[43mmetadata\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmetadata\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 215\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrun_name\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 216\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 217\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:315\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 301\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrun\u001b[39m(\n\u001b[1;32m 302\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 303\u001b[0m tool_input: Union[\u001b[38;5;28mstr\u001b[39m, Dict],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 313\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Any:\n\u001b[1;32m 314\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Run the tool.\"\"\"\u001b[39;00m\n\u001b[0;32m--> 315\u001b[0m parsed_input \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse_input\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtool_input\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 316\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mverbose \u001b[38;5;129;01mand\u001b[39;00m verbose \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 317\u001b[0m verbose_ \u001b[38;5;241m=\u001b[39m verbose\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:250\u001b[0m, in \u001b[0;36mBaseTool._parse_input\u001b[0;34m(self, tool_input)\u001b[0m\n\u001b[1;32m 248\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 249\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m input_args \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 250\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[43minput_args\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparse_obj\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtool_input\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m {\n\u001b[1;32m 252\u001b[0m k: \u001b[38;5;28mgetattr\u001b[39m(result, k)\n\u001b[1;32m 253\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m result\u001b[38;5;241m.\u001b[39mdict()\u001b[38;5;241m.\u001b[39mitems()\n\u001b[1;32m 254\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m k \u001b[38;5;129;01min\u001b[39;00m tool_input\n\u001b[1;32m 255\u001b[0m }\n\u001b[1;32m 256\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m tool_input\n", + "File \u001b[0;32m~/langchain/.venv/lib/python3.9/site-packages/pydantic/v1/main.py:526\u001b[0m, in \u001b[0;36mBaseModel.parse_obj\u001b[0;34m(cls, obj)\u001b[0m\n\u001b[1;32m 524\u001b[0m exc \u001b[38;5;241m=\u001b[39m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m expected dict not \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mobj\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 525\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ValidationError([ErrorWrapper(exc, loc\u001b[38;5;241m=\u001b[39mROOT_KEY)], \u001b[38;5;28mcls\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01me\u001b[39;00m\n\u001b[0;32m--> 526\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mobj\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/.venv/lib/python3.9/site-packages/pydantic/v1/main.py:341\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 339\u001b[0m values, fields_set, validation_error \u001b[38;5;241m=\u001b[39m validate_model(__pydantic_self__\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m, data)\n\u001b[1;32m 340\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m validation_error:\n\u001b[0;32m--> 341\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m validation_error\n\u001b[1;32m 342\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 343\u001b[0m object_setattr(__pydantic_self__, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__dict__\u001b[39m\u001b[38;5;124m'\u001b[39m, values)\n", + "\u001b[0;31mValidationError\u001b[0m: 1 validation error for complex_toolSchemaSchema\ndict_arg\n field required (type=value_error.missing)" + ] + } + ], + "source": [ + "chain.invoke(\n", + " \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "890d989d-2d39-4571-9a55-d3496b9b5d27", + "metadata": {}, + "source": [ + "## Try/except tool call\n", + "\n", + "The simplest way to more gracefully handle errors is to try/except the tool-calling step and return a helpful message on errors:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8fedb550-683d-45ae-8876-ae7acb332019", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any\n", + "\n", + "from langchain_core.runnables import RunnableConfig\n", + "\n", + "\n", + "def try_except_tool(tool_args: dict, config: RunnableConfig) -> Runnable:\n", + " try:\n", + " complex_tool.invoke(tool_args, config=config)\n", + " except Exception as e:\n", + " return f\"Calling tool with arguments:\\n\\n{tool_args}\\n\\nraised the following error:\\n\\n{type(e)}: {e}\"\n", + "\n", + "\n", + "chain = (\n", + " model_with_tools\n", + " | JsonOutputKeyToolsParser(key_name=\"complex_tool\", return_single=True)\n", + " | try_except_tool\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "71a2c98d-c0be-4c0a-bb3d-41ad4596526c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calling tool with arguments:\n", + "\n", + "{'int_arg': 5, 'float_arg': 2.1}\n", + "\n", + "raised the following error:\n", + "\n", + "<class 'pydantic.v1.error_wrappers.ValidationError'>: 1 validation error for complex_toolSchemaSchema\n", + "dict_arg\n", + " field required (type=value_error.missing)\n" + ] + } + ], + "source": [ + "print(\n", + " chain.invoke(\n", + " \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3b2f6393-cb47-49d0-921c-09550a049fe4", + "metadata": {}, + "source": [ + "## Fallbacks\n", + "\n", + "We can also try to fallback to a better model in the event of a tool invocation error. In this case we'll fall back to an identical chain that uses `gpt-4-1106-preview` instead of `gpt-3.5-turbo`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "02cc4223-35fa-4240-976a-012299ca703c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.5" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = (\n", + " model_with_tools\n", + " | JsonOutputKeyToolsParser(key_name=\"complex_tool\", return_single=True)\n", + " | complex_tool\n", + ")\n", + "better_model = ChatOpenAI(model=\"gpt-4-1106-preview\", temperature=0).bind(\n", + " tools=[format_tool_to_openai_tool(complex_tool)],\n", + " tool_choice={\"type\": \"function\", \"function\": {\"name\": \"complex_tool\"}},\n", + ")\n", + "better_chain = (\n", + " better_model\n", + " | JsonOutputKeyToolsParser(key_name=\"complex_tool\", return_single=True)\n", + " | complex_tool\n", + ")\n", + "\n", + "chain_with_fallback = chain.with_fallbacks([better_chain])\n", + "chain_with_fallback.invoke(\n", + " \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "412f8c4e-cc83-4d87-84a1-5ba2f8edb1e9", + "metadata": {}, + "source": [ + "Looking at the [Langsmith trace](https://smith.langchain.com/public/241e1266-8555-4d49-99dc-b8df46109c39/r) for this chain run, we can see that the first chain call fails as expected and it's the fallback that succeeds." + ] + }, + { + "cell_type": "markdown", + "id": "304b59cd-cd25-4205-9769-36595c8f3b59", + "metadata": {}, + "source": [ + "## Retry with exception\n", + "\n", + "To take things one step further, we can try to automatically re-run the chain with the exception passed in, so that the model may be able to correct its behavior:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b5659956-9454-468a-9753-a3ff9052b8f5", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from typing import Any\n", + "\n", + "from langchain_core.messages import AIMessage, HumanMessage, ToolMessage\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "\n", + "class CustomToolException(Exception):\n", + " \"\"\"Custom LangChain tool exception.\"\"\"\n", + "\n", + " def __init__(self, tool_call: dict, exception: Exception) -> None:\n", + " super().__init__()\n", + " self.tool_call = tool_call\n", + " self.exception = exception\n", + "\n", + "\n", + "def tool_custom_exception(tool_call: dict, config: RunnableConfig) -> Runnable:\n", + " try:\n", + " return complex_tool.invoke(tool_call[\"args\"], config=config)\n", + " except Exception as e:\n", + " raise CustomToolException(tool_call, e)\n", + "\n", + "\n", + "def exception_to_messages(inputs: dict) -> dict:\n", + " exception = inputs.pop(\"exception\")\n", + " tool_call = {\n", + " \"type\": \"function\",\n", + " \"function\": {\n", + " \"name\": \"complex_tool\",\n", + " \"arguments\": json.dumps(exception.tool_call[\"args\"]),\n", + " },\n", + " \"id\": exception.tool_call[\"id\"],\n", + " }\n", + "\n", + " # Add historical messages to the original input, so the model knows that it made a mistake with the last tool call.\n", + " messages = [\n", + " AIMessage(content=\"\", additional_kwargs={\"tool_calls\": [tool_call]}),\n", + " ToolMessage(tool_call_id=tool_call[\"id\"], content=str(exception.exception)),\n", + " HumanMessage(\n", + " content=\"The last tool calls raised exceptions. Try calling the tools again with corrected arguments.\"\n", + " ),\n", + " ]\n", + " inputs[\"last_output\"] = messages\n", + " return inputs\n", + "\n", + "\n", + "# We add a last_output MessagesPlaceholder to our prompt which if not passed in doesn't\n", + "# affect the prompt at all, but gives us the option to insert an arbitrary list of Messages\n", + "# into the prompt if needed. We'll use this on retries to insert the error message.\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"human\", \"{input}\"), MessagesPlaceholder(\"last_output\", optional=True)]\n", + ")\n", + "chain = (\n", + " prompt\n", + " | model_with_tools\n", + " | JsonOutputKeyToolsParser(\n", + " key_name=\"complex_tool\", return_id=True, return_single=True\n", + " )\n", + " | tool_custom_exception\n", + ")\n", + "\n", + "# If the initial chain call fails, we rerun it withe the exception passed in as a message.\n", + "self_correcting_chain = chain.with_fallbacks(\n", + " [exception_to_messages | chain], exception_key=\"exception\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4c45f5bd-cbb4-47d5-b4b6-aec50673c750", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.5" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "self_correcting_chain.invoke(\n", + " {\n", + " \"input\": \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "50d269a9-3cab-4a37-ba2f-805296453627", + "metadata": {}, + "source": [ + "And our chain succeeds! Looking at the [LangSmith trace](https://smith.langchain.com/public/b780b740-daf5-43aa-a217-6d4600aba41b/r), we can see that indeed our initial chain still fails, and it's only on retrying that the chain succeeds." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/langchain/langchain/chains/openai_functions/base.py b/libs/langchain/langchain/chains/openai_functions/base.py index 426a41a513150..e94f1fded2433 100644 --- a/libs/langchain/langchain/chains/openai_functions/base.py +++ b/libs/langchain/langchain/chains/openai_functions/base.py @@ -9,6 +9,7 @@ Union, ) +from langchain_core._api import deprecated from langchain_core.output_parsers import ( BaseGenerationOutputParser, BaseLLMOutputParser, @@ -106,7 +107,7 @@ def create_openai_fn_runnable( from typing import Optional - from langchain.chains.openai_functions import create_openai_fn_chain + from langchain.chains.openai_functions import create_openai_fn_runnable from langchain_community.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.pydantic_v1 import BaseModel, Field @@ -180,7 +181,7 @@ def create_structured_output_runnable( from typing import Optional - from langchain.chains.openai_functions import create_structured_output_chain + from langchain.chains.openai_functions import create_structured_output_runnable from langchain_community.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.pydantic_v1 import BaseModel, Field @@ -200,7 +201,7 @@ class Dog(BaseModel): ("human", "Tip: Make sure to answer in the correct format"), ] ) - chain = create_structured_output_chain(Dog, llm, prompt) + chain = create_structured_output_runnable(Dog, llm, prompt) chain.invoke({"input": "Harry was a chubby brown beagle who loved chicken"}) # -> Dog(name="Harry", color="brown", fav_food="chicken") """ # noqa: E501 @@ -236,6 +237,7 @@ class _OutputFormatter(BaseModel): """ --- Legacy --- """ +@deprecated(since="0.1.1", removal="0.2.0", alternative="create_openai_fn_runnable") def create_openai_fn_chain( functions: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable]], llm: BaseLanguageModel, @@ -336,6 +338,9 @@ class RecordDog(BaseModel): return llm_chain +@deprecated( + since="0.1.1", removal="0.2.0", alternative="create_structured_output_runnable" +) def create_structured_output_chain( output_schema: Union[Dict[str, Any], Type[BaseModel]], llm: BaseLanguageModel, diff --git a/libs/langchain/langchain/output_parsers/openai_tools.py b/libs/langchain/langchain/output_parsers/openai_tools.py index 3c8127c8fb123..bea4b12a3c2bf 100644 --- a/libs/langchain/langchain/output_parsers/openai_tools.py +++ b/libs/langchain/langchain/output_parsers/openai_tools.py @@ -1,5 +1,6 @@ import copy import json +from json import JSONDecodeError from typing import Any, List, Type from langchain_core.exceptions import OutputParserException @@ -13,6 +14,16 @@ class JsonOutputToolsParser(BaseGenerationOutputParser[Any]): """Parse tools from OpenAI response.""" + strict: bool = False + """Whether to allow non-JSON-compliant strings. + + See: https://docs.python.org/3/library/json.html#encoders-and-decoders + + Useful when the parsed output may include unicode characters or new lines. + """ + return_id: bool = False + """Whether to return the tool call id.""" + def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: generation = result[0] if not isinstance(generation, ChatGeneration): @@ -26,16 +37,30 @@ def parse_result(self, result: List[Generation], *, partial: bool = False) -> An return [] final_tools = [] + exceptions = [] for tool_call in tool_calls: if "function" not in tool_call: - pass - function_args = tool_call["function"]["arguments"] - final_tools.append( - { - "type": tool_call["function"]["name"], - "args": json.loads(function_args), - } - ) + continue + try: + function_args = json.loads( + tool_call["function"]["arguments"], strict=self.strict + ) + except JSONDecodeError as e: + exceptions.append( + f"Function {tool_call['function']['name']} arguments:\n\n" + f"{tool_call['function']['arguments']}\n\nare not valid JSON. " + f"Received JSONDecodeError {e}" + ) + continue + parsed = { + "type": tool_call["function"]["name"], + "args": function_args, + } + if self.return_id: + parsed["id"] = tool_call["id"] + final_tools.append(parsed) + if exceptions: + raise OutputParserException("\n\n".join(exceptions)) return final_tools @@ -44,10 +69,17 @@ class JsonOutputKeyToolsParser(JsonOutputToolsParser): key_name: str """The type of tools to return.""" + return_single: bool = False + """Whether to return only the first tool call.""" def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: results = super().parse_result(result) - return [res["args"] for res in results if results["type"] == self.key_name] + results = [res for res in results if res["type"] == self.key_name] + if not self.return_id: + results = [res["args"] for res in results] + if self.return_single: + return results[0] if results else None + return results class PydanticToolsParser(JsonOutputToolsParser): diff --git a/poetry.lock b/poetry.lock index bd91830a98f9c..6cb99a0e57466 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -742,6 +742,17 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + [[package]] name = "dnspython" version = "2.4.2" @@ -1036,6 +1047,62 @@ files = [ docs = ["Sphinx"] 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.2" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, +] + +[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,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[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.*)"] + [[package]] name = "idna" version = "3.4" @@ -1627,7 +1694,7 @@ files = [ [[package]] name = "langchain" -version = "0.0.352" +version = "0.1.0" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -1639,9 +1706,9 @@ aiohttp = "^3.8.3" async-timeout = {version = "^4.0.0", markers = "python_version < \"3.11\""} dataclasses-json = ">= 0.5.7, < 0.7" jsonpatch = "^1.33" -langchain-community = ">=0.0.2,<0.1" -langchain-core = "^0.1" -langsmith = "~0.0.70" +langchain-community = ">=0.0.9,<0.1" +langchain-core = ">=0.1.7,<0.2" +langsmith = "~0.0.77" numpy = "^1" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -1657,7 +1724,7 @@ cli = ["typer (>=0.9.0,<0.10.0)"] cohere = ["cohere (>=4,<5)"] docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] embeddings = ["sentence-transformers (>=2,<3)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] javascript = ["esprima (>=4.0.1,<5.0.0)"] llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0)"] @@ -1670,7 +1737,7 @@ url = "libs/langchain" [[package]] name = "langchain-community" -version = "0.0.6" +version = "0.0.11" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.8.1,<4.0" @@ -1680,7 +1747,7 @@ develop = true [package.dependencies] aiohttp = "^3.8.3" dataclasses-json = ">= 0.5.7, < 0.7" -langchain-core = "^0.1" +langchain-core = ">=0.1.8,<0.2" langsmith = "~0.0.63" numpy = "^1" PyYAML = ">=5.3" @@ -1690,7 +1757,7 @@ tenacity = "^8.1.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] [package.source] type = "directory" @@ -1698,7 +1765,7 @@ url = "libs/community" [[package]] name = "langchain-core" -version = "0.1.3" +version = "0.1.8" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -1724,7 +1791,7 @@ url = "libs/core" [[package]] name = "langchain-experimental" -version = "0.0.47" +version = "0.0.48" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -1732,8 +1799,8 @@ files = [] develop = true [package.dependencies] -langchain = ">=0.0.350,<0.1" -langchain-core = "^0.1" +langchain = "^0.1" +langchain-core = "^0.1.7" [package.extras] extended-testing = ["faker (>=19.3.1,<20.0.0)", "jinja2 (>=3,<4)", "presidio-analyzer (>=2.2.33,<3.0.0)", "presidio-anonymizer (>=2.2.33,<3.0.0)", "sentence-transformers (>=2,<3)", "vowpal-wabbit-next (==0.6.0)"] @@ -1742,15 +1809,34 @@ extended-testing = ["faker (>=19.3.1,<20.0.0)", "jinja2 (>=3,<4)", "presidio-ana type = "directory" url = "libs/experimental" +[[package]] +name = "langchain-openai" +version = "0.0.2" +description = "An integration package connecting OpenAI and LangChain" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [] +develop = true + +[package.dependencies] +langchain-core = ">=0.1.7,<0.2" +numpy = "^1" +openai = "^1.6.1" +tiktoken = "^0.5.2" + +[package.source] +type = "directory" +url = "libs/partners/openai" + [[package]] name = "langsmith" -version = "0.0.74" +version = "0.0.79" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.74-py3-none-any.whl", hash = "sha256:5d573dae3c59c84aca9e4d30a79ef49906151a32bf43830ff83863a825993ac2"}, - {file = "langsmith-0.0.74.tar.gz", hash = "sha256:249dae3625580fc9c1477be447ecd8dc1db76d1c8b59d0296abc027a37a0ce0b"}, + {file = "langsmith-0.0.79-py3-none-any.whl", hash = "sha256:be0374e913c36d9f6a13dd6b6e20a506066d5a0f3abfd476f9cf9e0b086ed744"}, + {file = "langsmith-0.0.79.tar.gz", hash = "sha256:d32639ccd18a92533b302f6f482255619afc8eb007fff91e37ee699d947c5e29"}, ] [package.dependencies] @@ -2354,6 +2440,29 @@ sphinx = ">=1.8" [package.extras] testing = ["matplotlib", "pytest", "pytest-cov"] +[[package]] +name = "openai" +version = "1.7.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.7.0-py3-none-any.whl", hash = "sha256:2282e8e15acb05df79cccba330c025b8e84284c7ec1f3fa31f167a8479066333"}, + {file = "openai-1.7.0.tar.gz", hash = "sha256:f2a8dcb739e8620c9318a2c6304ea72aebb572ba02fa1d586344405e80d567d3"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + [[package]] name = "overrides" version = "7.4.0" @@ -3034,6 +3143,108 @@ files = [ attrs = ">=22.2.0" rpds-py = ">=0.7.0" +[[package]] +name = "regex" +version = "2023.12.25" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -3675,6 +3886,58 @@ tornado = ">=6.1.0" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] +[[package]] +name = "tiktoken" +version = "0.5.2" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c4e654282ef05ec1bd06ead22141a9a1687991cef2c6a81bdd1284301abc71d"}, + {file = "tiktoken-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b3134aa24319f42c27718c6967f3c1916a38a715a0fa73d33717ba121231307"}, + {file = "tiktoken-0.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6092e6e77730929c8c6a51bb0d7cfdf1b72b63c4d033d6258d1f2ee81052e9e5"}, + {file = "tiktoken-0.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ad8ae2a747622efae75837abba59be6c15a8f31b4ac3c6156bc56ec7a8e631"}, + {file = "tiktoken-0.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51cba7c8711afa0b885445f0637f0fcc366740798c40b981f08c5f984e02c9d1"}, + {file = "tiktoken-0.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3d8c7d2c9313f8e92e987d585ee2ba0f7c40a0de84f4805b093b634f792124f5"}, + {file = "tiktoken-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:692eca18c5fd8d1e0dde767f895c17686faaa102f37640e884eecb6854e7cca7"}, + {file = "tiktoken-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:138d173abbf1ec75863ad68ca289d4da30caa3245f3c8d4bfb274c4d629a2f77"}, + {file = "tiktoken-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7388fdd684690973fdc450b47dfd24d7f0cbe658f58a576169baef5ae4658607"}, + {file = "tiktoken-0.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a114391790113bcff670c70c24e166a841f7ea8f47ee2fe0e71e08b49d0bf2d4"}, + {file = "tiktoken-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca96f001e69f6859dd52926d950cfcc610480e920e576183497ab954e645e6ac"}, + {file = "tiktoken-0.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:15fed1dd88e30dfadcdd8e53a8927f04e1f6f81ad08a5ca824858a593ab476c7"}, + {file = "tiktoken-0.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f8e692db5756f7ea8cb0cfca34638316dcf0841fb8469de8ed7f6a015ba0b0"}, + {file = "tiktoken-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:bcae1c4c92df2ffc4fe9f475bf8148dbb0ee2404743168bbeb9dcc4b79dc1fdd"}, + {file = "tiktoken-0.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b76a1e17d4eb4357d00f0622d9a48ffbb23401dcf36f9716d9bd9c8e79d421aa"}, + {file = "tiktoken-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01d8b171bb5df4035580bc26d4f5339a6fd58d06f069091899d4a798ea279d3e"}, + {file = "tiktoken-0.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42adf7d4fb1ed8de6e0ff2e794a6a15005f056a0d83d22d1d6755a39bffd9e7f"}, + {file = "tiktoken-0.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3f894dbe0adb44609f3d532b8ea10820d61fdcb288b325a458dfc60fefb7db"}, + {file = "tiktoken-0.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:58ccfddb4e62f0df974e8f7e34a667981d9bb553a811256e617731bf1d007d19"}, + {file = "tiktoken-0.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58902a8bad2de4268c2a701f1c844d22bfa3cbcc485b10e8e3e28a050179330b"}, + {file = "tiktoken-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:5e39257826d0647fcac403d8fa0a474b30d02ec8ffc012cfaf13083e9b5e82c5"}, + {file = "tiktoken-0.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bde3b0fbf09a23072d39c1ede0e0821f759b4fa254a5f00078909158e90ae1f"}, + {file = "tiktoken-0.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2ddee082dcf1231ccf3a591d234935e6acf3e82ee28521fe99af9630bc8d2a60"}, + {file = "tiktoken-0.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35c057a6a4e777b5966a7540481a75a31429fc1cb4c9da87b71c8b75b5143037"}, + {file = "tiktoken-0.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c4a049b87e28f1dc60509f8eb7790bc8d11f9a70d99b9dd18dfdd81a084ffe6"}, + {file = "tiktoken-0.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5bf5ce759089f4f6521ea6ed89d8f988f7b396e9f4afb503b945f5c949c6bec2"}, + {file = "tiktoken-0.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0c964f554af1a96884e01188f480dad3fc224c4bbcf7af75d4b74c4b74ae0125"}, + {file = "tiktoken-0.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:368dd5726d2e8788e47ea04f32e20f72a2012a8a67af5b0b003d1e059f1d30a3"}, + {file = "tiktoken-0.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2deef9115b8cd55536c0a02c0203512f8deb2447f41585e6d929a0b878a0dd2"}, + {file = "tiktoken-0.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ed7d380195affbf886e2f8b92b14edfe13f4768ff5fc8de315adba5b773815e"}, + {file = "tiktoken-0.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76fce01309c8140ffe15eb34ded2bb94789614b7d1d09e206838fc173776a18"}, + {file = "tiktoken-0.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60a5654d6a2e2d152637dd9a880b4482267dfc8a86ccf3ab1cec31a8c76bfae8"}, + {file = "tiktoken-0.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41d4d3228e051b779245a8ddd21d4336f8975563e92375662f42d05a19bdff41"}, + {file = "tiktoken-0.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c1cdec2c92fcde8c17a50814b525ae6a88e8e5b02030dc120b76e11db93f13"}, + {file = "tiktoken-0.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:84ddb36faedb448a50b246e13d1b6ee3437f60b7169b723a4b2abad75e914f3e"}, + {file = "tiktoken-0.5.2.tar.gz", hash = "sha256:f54c581f134a8ea96ce2023ab221d4d4d81ab614efa0b2fbce926387deb56c80"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + [[package]] name = "tinycss2" version = "1.2.1" @@ -3746,6 +4009,26 @@ files = [ {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, ] +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "5.11.1" @@ -3998,4 +4281,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "581c178796dbb76589632e687d353a336ca23b3cdda7075720660b479dc85fa2" +content-hash = "0ee5840cf8fda328bd967f6bd3c9be4e698ccfdd4b8b14c970bad7f2d338ec81" diff --git a/pyproject.toml b/pyproject.toml index b133ad83c374c..1220209cb7fdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ langchain-core = { path = "libs/core/", develop = true } langchain-community = { path = "libs/community/", develop = true } langchain = { path = "libs/langchain/", develop = true } langchain-experimental = { path = "libs/experimental/", develop = true } +langchain-openai = { path = "libs/partners/openai", develop = true } [tool.poetry.group.codespell.dependencies] codespell = "^2.2.0" @@ -44,6 +45,7 @@ langchain-core = { path = "libs/core/", develop = true } langchain-community = { path = "libs/community/", develop = true } langchain = { path = "libs/langchain/", develop = true } langchain-experimental = { path = "libs/experimental/", develop = true } +langchain-openai = { path = "libs/partners/openai", develop = true } [tool.poetry.group.test.dependencies] From 4df14a61fc871a9ac6748636501e342989b85259 Mon Sep 17 00:00:00 2001 From: Leonid Kuligin <lkuligin@yandex.ru> Date: Wed, 17 Jan 2024 02:01:26 +0100 Subject: [PATCH 043/215] google-vertexai[minor]: add function calling on VertexAI (#15822) Replace this entire comment with: - **Description:** Description: added support for tools on VertexAI - **Issue:** #15073 - **Twitter handle:** lkuligin --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- libs/core/langchain_core/load/mapping.py | 6 +- .../langchain_core/utils/function_calling.py | 2 +- libs/partners/google-vertexai/Makefile | 4 +- .../langchain_google_vertexai/chat_models.py | 126 ++- .../functions_utils.py | 56 ++ .../langchain_google_vertexai/llms.py | 9 + libs/partners/google-vertexai/poetry.lock | 818 +++++++++++++++++- libs/partners/google-vertexai/pyproject.toml | 7 +- .../tests/integration_tests/test_tools.py | 166 ++++ 9 files changed, 1159 insertions(+), 35 deletions(-) create mode 100644 libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py create mode 100644 libs/partners/google-vertexai/tests/integration_tests/test_tools.py diff --git a/libs/core/langchain_core/load/mapping.py b/libs/core/langchain_core/load/mapping.py index 454879230d34e..755428c363ffb 100644 --- a/libs/core/langchain_core/load/mapping.py +++ b/libs/core/langchain_core/load/mapping.py @@ -253,9 +253,8 @@ "ChatGooglePalm", ), ("langchain", "chat_models", "vertexai", "ChatVertexAI"): ( - "langchain", + "langchain_google_vertexai", "chat_models", - "vertexai", "ChatVertexAI", ), ("langchain", "schema", "output", "ChatGenerationChunk"): ( @@ -337,9 +336,8 @@ "Replicate", ), ("langchain", "llms", "vertexai", "VertexAI"): ( - "langchain", + "langchain_vertexai", "llms", - "vertexai", "VertexAI", ), ("langchain", "output_parsers", "combining", "CombiningOutputParser"): ( diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index b943eeaef5e8e..0646a8aa43241 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -28,7 +28,7 @@ class FunctionDescription(TypedDict): - """Representation of a callable function to the OpenAI API.""" + """Representation of a callable function to send to an LLM.""" name: str """The name of the function.""" diff --git a/libs/partners/google-vertexai/Makefile b/libs/partners/google-vertexai/Makefile index ceb9823f9d1be..a1a4607ae611a 100644 --- a/libs/partners/google-vertexai/Makefile +++ b/libs/partners/google-vertexai/Makefile @@ -6,7 +6,9 @@ all: help # Define a variable for the test file path. TEST_FILE ?= tests/unit_tests/ -test: +test_integration: TEST_FILE = tests/integration_tests/ + +test test_integration: poetry run pytest $(TEST_FILE) tests: diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py b/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py index 49f28d0bf8ca1..72f23815d7d7f 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py @@ -2,6 +2,7 @@ from __future__ import annotations import base64 +import json import logging import re from dataclasses import dataclass, field @@ -9,6 +10,8 @@ from urllib.parse import urlparse import requests +from google.cloud.aiplatform_v1beta1.types.content import Part as GapicPart +from google.cloud.aiplatform_v1beta1.types.tool import FunctionCall from langchain_core.callbacks import ( AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun, @@ -21,6 +24,7 @@ AIMessage, AIMessageChunk, BaseMessage, + FunctionMessage, HumanMessage, SystemMessage, ) @@ -35,6 +39,7 @@ InputOutputTextPair, ) from vertexai.preview.generative_models import ( # type: ignore + Candidate, Content, GenerativeModel, Image, @@ -42,12 +47,15 @@ ) from langchain_google_vertexai._utils import ( - is_codey_model, - is_gemini_model, load_image_from_gcs, ) +from langchain_google_vertexai.functions_utils import ( + _format_tools_to_vertex_tool, +) from langchain_google_vertexai.llms import ( _VertexAICommon, + is_codey_model, + is_gemini_model, ) logger = logging.getLogger(__name__) @@ -139,23 +147,46 @@ def _convert_to_prompt(part: Union[str, Dict]) -> Part: raise ValueError("Only text and image_url types are supported!") return Part.from_image(image) + def _convert_to_parts(message: BaseMessage) -> List[Part]: + raw_content = message.content + if isinstance(raw_content, str): + raw_content = [raw_content] + return [_convert_to_prompt(part) for part in raw_content] + vertex_messages = [] for i, message in enumerate(history): if i == 0 and isinstance(message, SystemMessage): raise ValueError("SystemMessages are not yet supported!") elif isinstance(message, AIMessage): + raw_function_call = message.additional_kwargs.get("function_call") role = "model" + if raw_function_call: + function_call = FunctionCall( + { + "name": raw_function_call["name"], + "args": json.loads(raw_function_call["arguments"]), + } + ) + gapic_part = GapicPart(function_call=function_call) + parts = [Part._from_gapic(gapic_part)] elif isinstance(message, HumanMessage): role = "user" + parts = _convert_to_parts(message) + elif isinstance(message, FunctionMessage): + role = "user" + parts = [ + Part.from_function_response( + name=message.name, + response={ + "content": message.content, + }, + ) + ] else: raise ValueError( f"Unexpected message with type {type(message)} at the position {i}." ) - raw_content = message.content - if isinstance(raw_content, str): - raw_content = [raw_content] - parts = [_convert_to_prompt(part) for part in raw_content] vertex_message = Content(role=role, parts=parts) vertex_messages.append(vertex_message) return vertex_messages @@ -201,6 +232,25 @@ def _get_question(messages: List[BaseMessage]) -> HumanMessage: return question +def _parse_response_candidate(response_candidate: "Candidate") -> AIMessage: + try: + content = response_candidate.text + except ValueError: + content = "" + + additional_kwargs = {} + first_part = response_candidate.content.parts[0] + if first_part.function_call: + function_call = {"name": first_part.function_call.name} + + # dump to match other function calling llm for now + function_call["arguments"] = json.dumps( + {k: first_part.function_call.args[k] for k in first_part.function_call.args} + ) + additional_kwargs["function_call"] = function_call + return AIMessage(content=content, additional_kwargs=additional_kwargs) + + class ChatVertexAI(_VertexAICommon, BaseChatModel): """`Vertex AI` Chat large language models API.""" @@ -208,6 +258,15 @@ class ChatVertexAI(_VertexAICommon, BaseChatModel): "Underlying model name." examples: Optional[List[BaseMessage]] = None + @classmethod + def is_lc_serializable(self) -> bool: + return True + + @classmethod + def get_lc_namespace(cls) -> List[str]: + """Get the namespace of the langchain object.""" + return ["langchain", "chat_models", "vertexai"] + @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that the python package exists in environment.""" @@ -253,7 +312,6 @@ def _generate( ) return generate_from_stream(stream_iter) - question = _get_question(messages) params = self._prepare_params(stop=stop, stream=False, **kwargs) msg_params = {} if "candidate_count" in params: @@ -263,18 +321,27 @@ def _generate( history_gemini = _parse_chat_history_gemini(messages, project=self.project) message = history_gemini.pop() chat = self.client.start_chat(history=history_gemini) - response = chat.send_message(message, generation_config=params) + + # set param to `functions` until core tool/function calling implemented + raw_tools = params.pop("functions") if "functions" in params else None + tools = _format_tools_to_vertex_tool(raw_tools) if raw_tools else None + response = chat.send_message(message, generation_config=params, tools=tools) + generations = [ + ChatGeneration(message=_parse_response_candidate(c)) + for c in response.candidates + ] else: + question = _get_question(messages) history = _parse_chat_history(messages[:-1]) examples = kwargs.get("examples") or self.examples if examples: params["examples"] = _parse_examples(examples) chat = self._start_chat(history, **params) response = chat.send_message(question.content, **msg_params) - generations = [ - ChatGeneration(message=AIMessage(content=r.text)) - for r in response.candidates - ] + generations = [ + ChatGeneration(message=AIMessage(content=r.text)) + for r in response.candidates + ] return ChatResult(generations=generations) async def _agenerate( @@ -311,7 +378,16 @@ async def _agenerate( history_gemini = _parse_chat_history_gemini(messages, project=self.project) message = history_gemini.pop() chat = self.client.start_chat(history=history_gemini) - response = await chat.send_message_async(message, generation_config=params) + # set param to `functions` until core tool/function calling implemented + raw_tools = params.pop("functions") if "functions" in params else None + tools = _format_tools_to_vertex_tool(raw_tools) if raw_tools else None + response = await chat.send_message_async( + message, generation_config=params, tools=tools + ) + generations = [ + ChatGeneration(message=_parse_response_candidate(c)) + for c in response.candidates + ] else: question = _get_question(messages) history = _parse_chat_history(messages[:-1]) @@ -320,11 +396,10 @@ async def _agenerate( params["examples"] = _parse_examples(examples) chat = self._start_chat(history, **params) response = await chat.send_message_async(question.content, **msg_params) - - generations = [ - ChatGeneration(message=AIMessage(content=r.text)) - for r in response.candidates - ] + generations = [ + ChatGeneration(message=AIMessage(content=r.text)) + for r in response.candidates + ] return ChatResult(generations=generations) def _stream( @@ -339,9 +414,22 @@ def _stream( history_gemini = _parse_chat_history_gemini(messages, project=self.project) message = history_gemini.pop() chat = self.client.start_chat(history=history_gemini) + # set param to `functions` until core tool/function calling implemented + raw_tools = params.pop("functions") if "functions" in params else None + tools = _format_tools_to_vertex_tool(raw_tools) if raw_tools else None responses = chat.send_message( - message, stream=True, generation_config=params + message, stream=True, generation_config=params, tools=tools ) + for response in responses: + message = _parse_response_candidate(response.candidates[0]) + if run_manager: + run_manager.on_llm_new_token(message.content) + yield ChatGenerationChunk( + message=AIMessageChunk( + content=message.content, + additional_kwargs=message.additional_kwargs, + ) + ) else: question = _get_question(messages) history = _parse_chat_history(messages[:-1]) diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py new file mode 100644 index 0000000000000..8e6aed3da1951 --- /dev/null +++ b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py @@ -0,0 +1,56 @@ +from typing import List + +from langchain_core.tools import Tool +from langchain_core.utils.function_calling import FunctionDescription +from langchain_core.utils.json_schema import dereference_refs +from vertexai.preview.generative_models import ( # type: ignore + FunctionDeclaration, +) +from vertexai.preview.generative_models import ( + Tool as VertexTool, # type: ignore +) + + +def _format_tool_to_vertex_function(tool: Tool) -> FunctionDescription: + "Format tool into the Vertex function API." + if tool.args_schema: + schema = dereference_refs(tool.args_schema.schema()) + schema.pop("definitions", None) + + return { + "name": tool.name or schema["title"], + "description": tool.description or schema["description"], + "parameters": { + "properties": { + k: { + "type": v["type"], + "description": v.get("description"), + } + for k, v in schema["properties"].items() + }, + "required": schema["required"], + "type": schema["type"], + }, + } + else: + return { + "name": tool.name, + "description": tool.description, + "parameters": { + "properties": { + "__arg1": {"type": "string"}, + }, + "required": ["__arg1"], + "type": "object", + }, + } + + +def _format_tools_to_vertex_tool(tools: List[Tool]) -> List[VertexTool]: + "Format tool into the Vertex Tool instance." + function_declarations = [] + for tool in tools: + func = _format_tool_to_vertex_function(tool) + function_declarations.append(FunctionDeclaration(**func)) + + return [VertexTool(function_declarations=function_declarations)] diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py index c8905c99a6a8e..c3ff12d906432 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py @@ -202,6 +202,15 @@ class VertexAI(_VertexAICommon, BaseLLM): tuned_model_name: Optional[str] = None "The name of a tuned model. If provided, model_name is ignored." + @classmethod + def is_lc_serializable(self) -> bool: + return True + + @classmethod + def get_lc_namespace(cls) -> List[str]: + """Get the namespace of the langchain object.""" + return ["langchain", "llms", "vertexai"] + @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that the python package exists in environment.""" diff --git a/libs/partners/google-vertexai/poetry.lock b/libs/partners/google-vertexai/poetry.lock index 3fd6f04c24924..4665a3f425a4f 100644 --- a/libs/partners/google-vertexai/poetry.lock +++ b/libs/partners/google-vertexai/poetry.lock @@ -1,5 +1,115 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "aiohttp" +version = "3.9.1" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, + {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, + {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, + {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, + {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, + {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, + {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, + {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, + {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, + {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, + {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, + {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "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.6.0" @@ -36,6 +146,36 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin 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", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[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 = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] name = "cachetools" version = "5.3.2" @@ -185,6 +325,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dataclasses-json" +version = "0.6.3" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "dataclasses_json-0.6.3-py3-none-any.whl", hash = "sha256:4aeb343357997396f6bca1acae64e486c3a723d8f5c76301888abeccf0c45176"}, + {file = "dataclasses_json-0.6.3.tar.gz", hash = "sha256:35cb40aae824736fdf959801356641836365219cfe14caeb115c39136f775d2a"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -213,6 +368,92 @@ files = [ [package.dependencies] python-dateutil = ">=2.7" +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "google-api-core" version = "2.15.0" @@ -243,6 +484,24 @@ grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +[[package]] +name = "google-api-python-client" +version = "2.114.0" +description = "Google API Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-python-client-2.114.0.tar.gz", hash = "sha256:e041bbbf60e682261281e9d64b4660035f04db1cccba19d1d68eebc24d1465ed"}, + {file = "google_api_python_client-2.114.0-py2.py3-none-any.whl", hash = "sha256:690e0bb67d70ff6dea4e8a5d3738639c105a478ac35da153d3b2a384064e9e1a"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" +google-auth = ">=1.19.0,<3.0.0.dev0" +google-auth-httplib2 = ">=0.1.0" +httplib2 = ">=0.15.0,<1.dev0" +uritemplate = ">=3.0.1,<5" + [[package]] name = "google-auth" version = "2.26.1" @@ -266,15 +525,30 @@ pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +description = "Google Authentication Library: httplib2 transport" +optional = false +python-versions = "*" +files = [ + {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, + {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, +] + +[package.dependencies] +google-auth = "*" +httplib2 = ">=0.19.0" + [[package]] name = "google-cloud-aiplatform" -version = "1.38.1" +version = "1.39.0" description = "Vertex AI API client library" optional = false python-versions = ">=3.8" files = [ - {file = "google-cloud-aiplatform-1.38.1.tar.gz", hash = "sha256:30439d914bb028443c0506cc0e6dd825cff5401aeb8233e13d8cfd77c3c87da1"}, - {file = "google_cloud_aiplatform-1.38.1-py2.py3-none-any.whl", hash = "sha256:5e1fcd1068dd2c4f0fc89aa616e34a8b9434eaa72ea6216f5036ef26f08bd448"}, + {file = "google-cloud-aiplatform-1.39.0.tar.gz", hash = "sha256:62d6accbf9035895736910bc980f0b2a819d5841ae8bc0c981457cc16c49ecd1"}, + {file = "google_cloud_aiplatform-1.39.0-py2.py3-none-any.whl", hash = "sha256:d7b5c44fbb10d34c7941c5f7aadf7ff480c1469e37eac5b305bc9821fa49f7ee"}, ] [package.dependencies] @@ -293,7 +567,7 @@ autologging = ["mlflow (>=1.27.0,<=2.1.1)"] cloud-profiler = ["tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"] datasets = ["pyarrow (>=10.0.1)", "pyarrow (>=3.0.0,<8.0dev)"] endpoint = ["requests (>=2.28.1)"] -full = ["cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<0.103.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (==0.0.11)", "google-vizier (==0.0.4)", "google-vizier (>=0.0.14)", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyyaml (==5.3.1)", "ray[default] (>=2.4,<2.5)", "ray[default] (>=2.5,<2.5.1)", "requests (>=2.28.1)", "starlette (>=0.17.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)"] +full = ["cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<0.103.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (==0.0.11)", "google-vizier (==0.0.4)", "google-vizier (>=0.0.14)", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyyaml (==5.3.1)", "ray[default] (>=2.4,<2.5)", "ray[default] (>=2.5,<2.5.1)", "requests (>=2.28.1)", "starlette (>=0.17.1)", "tensorflow (>=2.3.0,<2.15.0)", "tensorflow (>=2.3.0,<3.0.0dev)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)"] lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0dev)"] metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"] pipelines = ["pyyaml (==5.3.1)"] @@ -301,8 +575,8 @@ prediction = ["docker (>=5.0.3)", "fastapi (>=0.71.0,<0.103.1)", "httpx (>=0.23. preview = ["cloudpickle (<3.0)", "google-cloud-logging (<4.0)"] private-endpoints = ["requests (>=2.28.1)", "urllib3 (>=1.21.1,<1.27)"] ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "ray[default] (>=2.4,<2.5)", "ray[default] (>=2.5,<2.5.1)"] -tensorboard = ["tensorflow (>=2.3.0,<3.0.0dev)"] -testing = ["bigframes", "cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<0.103.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (==0.0.11)", "google-vizier (==0.0.4)", "google-vizier (>=0.0.14)", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "ipython", "kfp", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyfakefs", "pytest-asyncio", "pytest-xdist", "pyyaml (==5.3.1)", "ray[default] (>=2.4,<2.5)", "ray[default] (>=2.5,<2.5.1)", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<=2.12.0)", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost", "xgboost-ray"] +tensorboard = ["tensorflow (>=2.3.0,<2.15.0)"] +testing = ["bigframes", "cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<0.103.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (==0.0.11)", "google-vizier (==0.0.4)", "google-vizier (>=0.0.14)", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "ipython", "kfp", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyfakefs", "pytest-asyncio", "pytest-xdist", "pyyaml (==5.3.1)", "ray[default] (>=2.4,<2.5)", "ray[default] (>=2.5,<2.5.1)", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (>=2.3.0,<2.15.0)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<=2.12.0)", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost", "xgboost-ray"] vizier = ["google-vizier (==0.0.11)", "google-vizier (==0.0.4)", "google-vizier (>=0.0.14)", "google-vizier (>=0.1.6)"] xai = ["tensorflow (>=2.3.0,<3.0.0dev)"] @@ -509,6 +783,77 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "grpc-google-iam-v1" version = "0.13.0" @@ -607,6 +952,20 @@ googleapis-common-protos = ">=1.5.5" grpcio = ">=1.60.0" protobuf = ">=4.21.6" +[[package]] +name = "httplib2" +version = "0.22.0" +description = "A comprehensive HTTP client library." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, + {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, +] + +[package.dependencies] +pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} + [[package]] name = "idna" version = "3.6" @@ -654,9 +1013,78 @@ files = [ {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, ] +[[package]] +name = "langchain" +version = "0.1.1" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [] +develop = false + +[package.dependencies] +aiohttp = "^3.8.3" +async-timeout = {version = "^4.0.0", markers = "python_version < \"3.11\""} +dataclasses-json = ">= 0.5.7, < 0.7" +jsonpatch = "^1.33" +langchain-community = ">=0.0.13,<0.1" +langchain-core = ">=0.1.9,<0.2" +langsmith = "~0.0.77" +numpy = "^1" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = "^2" +SQLAlchemy = ">=1.4,<3" +tenacity = "^8.1.0" + +[package.extras] +all = [] +azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (<2)"] +clarifai = ["clarifai (>=9.1.0)"] +cli = ["typer (>=0.9.0,<0.10.0)"] +cohere = ["cohere (>=4,<5)"] +docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] +embeddings = ["sentence-transformers (>=2,<3)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +javascript = ["esprima (>=4.0.1,<5.0.0)"] +llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] +openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0)"] +qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +text-helpers = ["chardet (>=5.1.0,<6.0.0)"] + +[package.source] +type = "directory" +url = "../../langchain" + +[[package]] +name = "langchain-community" +version = "0.0.13" +description = "Community contributed LangChain integrations." +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_community-0.0.13-py3-none-any.whl", hash = "sha256:655196e446e7f37f4882221b6f3f791d6add28ea596d521ccf6f4507386b9a13"}, + {file = "langchain_community-0.0.13.tar.gz", hash = "sha256:cf66c6ff7fcbeb582f5e88ee00decea7fdeca5ccddda725046f28efc697c41a7"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +dataclasses-json = ">=0.5.7,<0.7" +langchain-core = ">=0.1.9,<0.2" +langsmith = ">=0.0.63,<0.1.0" +numpy = ">=1,<2" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +cli = ["typer (>=0.9.0,<0.10.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] + [[package]] name = "langchain-core" -version = "0.1.6" +version = "0.1.11" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -695,6 +1123,109 @@ files = [ pydantic = ">=1,<3" requests = ">=2,<3" +[[package]] +name = "marshmallow" +version = "3.20.2" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, + {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["pre-commit (>=2.4,<4.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + [[package]] name = "mypy" version = "0.991" @@ -756,6 +1287,47 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "numexpr" +version = "2.8.8" +description = "Fast numerical expression evaluator for NumPy" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numexpr-2.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85c9f79e346c26aa0d425ecfc9e5de7184567d5e48d0bdb02d468bb927e92525"}, + {file = "numexpr-2.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbac846f713b4c82333e6af0814ebea0b4e74dfb2649e76c58953fd4862322dd"}, + {file = "numexpr-2.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d7bfc8b77d8a7b04cd64ae42b62b3bf824a8c751ca235692bfd5231c6e90127"}, + {file = "numexpr-2.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:307b49fd15ef2ca292f381e67759e5b477410341f2f499a377234f1b42f529a6"}, + {file = "numexpr-2.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aab17d65751c039d13ed9d49c9a7517b130ef488c1885c4666af9b5c6ad59520"}, + {file = "numexpr-2.8.8-cp310-cp310-win32.whl", hash = "sha256:6459dc6ed6abcdeab3cd3667c79f29e4a0f0a02c29ad71ee5cff065e880ee9ef"}, + {file = "numexpr-2.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:22ccd67c0fbeae091f2c577f5b9c8046de6631d46b1cbe22aad46a08d2b42c2d"}, + {file = "numexpr-2.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:47c05007cd1c553515492c1a78b5477eaaba9cadc5d7b795d49f7aae53ccdf68"}, + {file = "numexpr-2.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4649c1dcf9b0c2ae0a7b767dbbbde4e05ee68480c1ba7f06fc7963f1f73acf4"}, + {file = "numexpr-2.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a82d710145b0fbaec919dde9c90ed9df1e6785625cc36d1c71f3a53112b66fc5"}, + {file = "numexpr-2.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a92f230dd9d6c42803f855970e93677b44290b6dad15cb6796fd85edee171ce"}, + {file = "numexpr-2.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ccef9b09432d59229c2a737882e55de7906006452003323e107576f264cec373"}, + {file = "numexpr-2.8.8-cp311-cp311-win32.whl", hash = "sha256:bf8c517bbbb82c07c23c17f9d52b4c9f86601f57d48e87c0cbda24af5907f4dd"}, + {file = "numexpr-2.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:4f01d71db6fdb97a68def5407e2dbd748eaea9d98929db08816de40aa4ae3084"}, + {file = "numexpr-2.8.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:76f0f010f9c6318bae213b21c5c0e381c2fc9c9ecb8b35f99f5030e7ac96c9ce"}, + {file = "numexpr-2.8.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f168b4b42d4cb120fe1993676dcf74b77a3e8e45b58855566da037cfd938ca3"}, + {file = "numexpr-2.8.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f031ac4e70f9ad867543bfbde8452e9d1a14f0525346b4b8bd4e5c0f1380a11c"}, + {file = "numexpr-2.8.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121b049b6909787111daf92919c052c4fd87b5691172e8f19f702b96f20aaafa"}, + {file = "numexpr-2.8.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ae264c35fa67cd510191ab8144f131fddd0f1d13413af710913ea6fc0c6aa61"}, + {file = "numexpr-2.8.8-cp312-cp312-win32.whl", hash = "sha256:399cb914b41c4027ba88a18f6b8ccfc3af5c32bc3b1758403a7c44c72530618a"}, + {file = "numexpr-2.8.8-cp312-cp312-win_amd64.whl", hash = "sha256:925927cd1f610593e7783d8f2e12e3d800d5928601e077e4910e2b50bde624b6"}, + {file = "numexpr-2.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd07793b074cc38e478637cbe738dff7d8eb92b5cf8ffaacff0c4f0bca5270a0"}, + {file = "numexpr-2.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:290f91c7ba7772abaf7107f3cc0601d93d6a3f21c13ee3da93f1b8a9ca3e8d39"}, + {file = "numexpr-2.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:296dc1f79d386166dec3bdb45f51caba29ffd8dc91db15447108c04d3001d921"}, + {file = "numexpr-2.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7badc50efbb2f1c8b78cd68089031e0fd29cbafa6a9e6d730533f22d88168406"}, + {file = "numexpr-2.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d83a542d9deefb050e389aacaddea0f09d68ec617dd37e45b9a7cfbcba6d729"}, + {file = "numexpr-2.8.8-cp39-cp39-win32.whl", hash = "sha256:17104051f0bd83fd350212e268d8b48017d5eff522b09b573fdbcc560c5e7ab3"}, + {file = "numexpr-2.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:12146521b1730073859a20454e75004e38cd0cb61333e763c58ef5171e101eb2"}, + {file = "numexpr-2.8.8.tar.gz", hash = "sha256:e76ce4d25372f46170cf7eb1ff14ed5d9c69a0b162a405063cbe481bafe3af34"}, +] + +[package.dependencies] +numpy = ">=1.13.3" + [[package]] name = "numpy" version = "1.24.4" @@ -1017,6 +1589,20 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pytest" version = "7.4.4" @@ -1308,6 +1894,93 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.25" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, + {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, + {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "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)"] +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.6.0" @@ -1383,6 +2056,32 @@ files = [ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + [[package]] name = "urllib3" version = "2.1.0" @@ -1438,7 +2137,110 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "7fcbc6833c982cb513d5655481487edf16d011a4366b7612bd2f0da98ade21b0" +content-hash = "a37b4d8ff76ba8de92cffb3d3ae396b82be8faf03f9a78bd6d96d01fb915b22c" diff --git a/libs/partners/google-vertexai/pyproject.toml b/libs/partners/google-vertexai/pyproject.toml index 22d2280243a85..34c2be261ddda 100644 --- a/libs/partners/google-vertexai/pyproject.toml +++ b/libs/partners/google-vertexai/pyproject.toml @@ -11,8 +11,8 @@ repository = "https://github.com/langchain-ai/langchain" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" -langchain-core = ">=0.1,<0.2" -google-cloud-aiplatform = "1.38.1" +langchain-core = ">=0.1.7,<0.2" +google-cloud-aiplatform = "^1.39.0" google-cloud-storage = "^2.14.0" types-requests = "^2.31.0.20231231" types-protobuf = "^4.24.0.4" @@ -41,6 +41,9 @@ codespell = "^2.2.0" optional = true [tool.poetry.group.test_integration.dependencies] +langchain = {path = "../../langchain"} +numexpr = {version = "^2.8.8", python = ">=3.9,<4.0"} +google-api-python-client = "^2.114.0" [tool.poetry.group.lint] optional = true diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_tools.py b/libs/partners/google-vertexai/tests/integration_tests/test_tools.py new file mode 100644 index 0000000000000..dda715879d923 --- /dev/null +++ b/libs/partners/google-vertexai/tests/integration_tests/test_tools.py @@ -0,0 +1,166 @@ +import os +from typing import List, Union + +from langchain_core.agents import AgentAction, AgentActionMessageLog, AgentFinish +from langchain_core.messages import AIMessageChunk +from langchain_core.output_parsers import BaseOutputParser +from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.tools import Tool + +from langchain_google_vertexai.chat_models import ChatVertexAI + + +class _TestOutputParser(BaseOutputParser): + def parse_result( + self, result: List[Generation], *, partial: bool = False + ) -> Union[AgentAction, AgentFinish]: + if not isinstance(result[0], ChatGeneration): + raise ValueError("This output parser only works on ChatGeneration output") + message = result[0].message + function_call = message.additional_kwargs.get("function_call", {}) + if function_call: + function_name = function_call["name"] + tool_input = function_call.get("arguments", {}) + + content_msg = f"responded: {message.content}\n" if message.content else "\n" + log_msg = ( + f"\nInvoking: `{function_name}` with `{tool_input}`\n{content_msg}\n" + ) + return AgentActionMessageLog( + tool=function_name, + tool_input=tool_input, + log=log_msg, + message_log=[message], + ) + + return AgentFinish( + return_values={"output": message.content}, log=str(message.content) + ) + + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + raise ValueError("Can only parse messages") + + +def test_tools() -> None: + from langchain.agents import AgentExecutor # type: ignore + from langchain.agents.format_scratchpad import ( # type: ignore + format_to_openai_function_messages, + ) + from langchain.chains import LLMMathChain # type: ignore + + llm = ChatVertexAI(model_name="gemini-pro") + math_chain = LLMMathChain.from_llm(llm=llm) + tools = [ + Tool( + name="Calculator", + func=math_chain.run, + description="useful for when you need to answer questions about math", + ) + ] + prompt = ChatPromptTemplate.from_messages( + [ + ("user", "{input}"), + MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + ) + llm_with_tools = llm.bind(functions=tools) + + agent = ( + { # type: ignore + "input": lambda x: x["input"], + "agent_scratchpad": lambda x: format_to_openai_function_messages( + x["intermediate_steps"] + ), + } + | prompt + | llm_with_tools + | _TestOutputParser() + ) + agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) + + response = agent_executor.invoke({"input": "What is 6 raised to the 0.43 power?"}) + print(response) + assert isinstance(response, dict) + assert response["input"] == "What is 6 raised to the 0.43 power?" + assert round(float(response["output"]), 3) == 2.161 + + +def test_stream() -> None: + from langchain.chains import LLMMathChain + + llm = ChatVertexAI(model_name="gemini-pro") + math_chain = LLMMathChain.from_llm(llm=llm) + tools = [ + Tool( + name="Calculator", + func=math_chain.run, + description="useful for when you need to answer questions about math", + ) + ] + response = list(llm.stream("What is 6 raised to the 0.43 power?", functions=tools)) + assert len(response) == 1 + # for chunk in response: + assert isinstance(response[0], AIMessageChunk) + assert "function_call" in response[0].additional_kwargs + + +def test_multiple_tools() -> None: + from langchain.agents import AgentExecutor # type: ignore + from langchain.agents.format_scratchpad import ( + format_to_openai_function_messages, # type: ignore + ) + from langchain.chains import LLMMathChain # type: ignore + from langchain.utilities import GoogleSearchAPIWrapper # type: ignore + + llm = ChatVertexAI(model_name="gemini-pro", max_output_tokens=1024) + math_chain = LLMMathChain.from_llm(llm=llm) + google_search_api_key = os.environ["GOOGLE_SEARCH_API_KEY"] + google_cse_id = os.environ["GOOGLE_CSE_ID"] + search = GoogleSearchAPIWrapper( + k=10, google_api_key=google_search_api_key, google_cse_id=google_cse_id + ) + tools = [ + Tool( + name="Calculator", + func=math_chain.run, + description="useful for when you need to answer questions about math", + ), + Tool( + name="Search", + func=search.run, + description=( + "useful for when you need to answer questions about current events. " + "You should ask targeted questions" + ), + ), + ] + prompt = ChatPromptTemplate.from_messages( + [ + ("user", "{input}"), + MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + ) + llm_with_tools = llm.bind(functions=tools) + + agent = ( + { # type: ignore + "input": lambda x: x["input"], + "agent_scratchpad": lambda x: format_to_openai_function_messages( + x["intermediate_steps"] + ), + } + | prompt + | llm_with_tools + | _TestOutputParser() + ) + agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) + + question = ( + "Who is Leo DiCaprio's girlfriend? What is her " + "current age raised to the 0.43 power?" + ) + response = agent_executor.invoke({"input": question}) + assert isinstance(response, dict) + assert response["input"] == question + assert "3.850" in response["output"] From f974eb5b8b642df9c0093cfd273de2cc5d372446 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Tue, 16 Jan 2024 17:13:51 -0800 Subject: [PATCH 044/215] docs: updated `Anyscale` page (#16107) - added description - fixed broken links - added setting instructions - added the Chat model reference --- docs/docs/integrations/providers/anyscale.mdx | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/docs/integrations/providers/anyscale.mdx b/docs/docs/integrations/providers/anyscale.mdx index a3b8c4cc4e318..087422e129beb 100644 --- a/docs/docs/integrations/providers/anyscale.mdx +++ b/docs/docs/integrations/providers/anyscale.mdx @@ -1,17 +1,34 @@ # Anyscale -This page covers how to use the Anyscale ecosystem within LangChain. -It is broken into two parts: installation and setup, and then references to specific Anyscale wrappers. +>[Anyscale](https://www.anyscale.com) is a platform to run, fine tune and scale LLMs via production-ready APIs. +> [Anyscale Endpoints](https://docs.anyscale.com/endpoints/overview) serve many open-source models in a cost-effective way. + +`Anyscale` also provides [an example](https://docs.anyscale.com/endpoints/model-serving/examples/langchain-integration) +how to setup `LangChain` with `Anyscale` for advanced chat agents. ## Installation and Setup + - Get an Anyscale Service URL, route and API key and set them as environment variables (`ANYSCALE_SERVICE_URL`,`ANYSCALE_SERVICE_ROUTE`, `ANYSCALE_SERVICE_TOKEN`). -- Please see [the Anyscale docs](https://docs.anyscale.com/productionize/services-v2/get-started) for more details. +- Please see [the Anyscale docs](https://www.anyscale.com/get-started) for more details. + +We have to install the `openai` package: + +```bash +pip install openai +``` + +## LLM + +See a [usage example](/docs/integrations/llms/anyscale). + +```python +from langchain_community.llms.anyscale import Anyscale +``` -## Wrappers +## Chat Models -### LLM +See a [usage example](/docs/integrations/chat/anyscale). -There exists an Anyscale LLM wrapper, which you can access with ```python -from langchain_community.llms import Anyscale +from langchain_community.chat_models.anyscale import ChatAnyscale ``` From c323742f4fda30e8bef5381f41f66192afb2b9d2 Mon Sep 17 00:00:00 2001 From: David <lms.davidromero@gmail.com> Date: Wed, 17 Jan 2024 02:48:37 +0100 Subject: [PATCH 045/215] mistralai[minor]: Add embeddings (#15282) - **Description:** Adds MistralAIEmbeddings class for embeddings, using the new official API. - **Dependencies:** mistralai - **Tag maintainer**: @efriis, @hwchase17 - **Twitter handle:** @LMS_David_RS Create `integrations/text_embedding/mistralai.ipynb`: an example notebook for MistralAIEmbeddings class Modify `embeddings/__init__.py`: Import the class Create `embeddings/mistralai.py`: The embedding class Create `integration_tests/embeddings/test_mistralai.py`: The test file. --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- .../text_embedding/mistralai.ipynb | 103 ++++ libs/partners/mistralai/README.md | 16 + libs/partners/mistralai/docs/embeddings.ipynb | 103 ++++ .../mistralai/langchain_mistralai/__init__.py | 3 +- .../langchain_mistralai/chat_models.py | 22 +- .../langchain_mistralai/embeddings.py | 141 ++++++ libs/partners/mistralai/poetry.lock | 478 ++---------------- libs/partners/mistralai/pyproject.toml | 2 +- .../integration_tests/test_embeddings.py | 19 + .../tests/unit_tests/test_embeddings.py | 10 + .../tests/unit_tests/test_imports.py | 2 +- 11 files changed, 461 insertions(+), 438 deletions(-) create mode 100644 docs/docs/integrations/text_embedding/mistralai.ipynb create mode 100644 libs/partners/mistralai/docs/embeddings.ipynb create mode 100644 libs/partners/mistralai/langchain_mistralai/embeddings.py create mode 100644 libs/partners/mistralai/tests/integration_tests/test_embeddings.py create mode 100644 libs/partners/mistralai/tests/unit_tests/test_embeddings.py diff --git a/docs/docs/integrations/text_embedding/mistralai.ipynb b/docs/docs/integrations/text_embedding/mistralai.ipynb new file mode 100644 index 0000000000000..55b15875bbd70 --- /dev/null +++ b/docs/docs/integrations/text_embedding/mistralai.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b14a24db", + "metadata": {}, + "source": [ + "# MistralAI\n", + "\n", + "This notebook explains how to use MistralAIEmbeddings, which is included in the langchain_mistralai package, to embed texts in langchain." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0ab948fc", + "metadata": {}, + "outputs": [], + "source": [ + "# pip install -U langchain-mistralai" + ] + }, + { + "cell_type": "markdown", + "id": "67c637ca", + "metadata": {}, + "source": [ + "## import the library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5709b030", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_mistralai import MistralAIEmbeddings" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1756b1ba", + "metadata": {}, + "outputs": [], + "source": [ + "embedding = MistralAIEmbeddings(mistral_api_key=\"your-api-key\")" + ] + }, + { + "cell_type": "markdown", + "id": "4a2a098d", + "metadata": {}, + "source": [ + "# Using the Embedding Model\n", + "With `MistralAIEmbeddings`, you can directly use the default model 'mistral-embed', or set a different one if available." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "584b9af5", + "metadata": {}, + "outputs": [], + "source": [ + "embedding.model = \"mistral-embed\" # or your preferred model if available" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "be18b873", + "metadata": {}, + "outputs": [], + "source": [ + "res_query = embedding.embed_query(\"The test information\")\n", + "res_document = embedding.embed_documents([\"test1\", \"another test\"])" + ] + } + ], + "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.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/partners/mistralai/README.md b/libs/partners/mistralai/README.md index 9ec32ed43218e..752544e10ce2c 100644 --- a/libs/partners/mistralai/README.md +++ b/libs/partners/mistralai/README.md @@ -39,3 +39,19 @@ await chat.ainvoke(messages) for chunk in chat.stream(messages): print(chunk.content, end="", flush=True) ``` + +## Embeddings + +With `MistralAIEmbeddings`, you can directly use the default model 'mistral-embed', or set a different one if available. + +### Choose model + +`embedding.model = 'mistral-embed'` + +### Simple query + +`res_query = embedding.embed_query("The test information")` + +### Documents + +`res_document = embedding.embed_documents(["test1", "another test"])` \ No newline at end of file diff --git a/libs/partners/mistralai/docs/embeddings.ipynb b/libs/partners/mistralai/docs/embeddings.ipynb new file mode 100644 index 0000000000000..33ed1137fdc9f --- /dev/null +++ b/libs/partners/mistralai/docs/embeddings.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b14a24db", + "metadata": {}, + "source": [ + "# MistralAIEmbeddings\n", + "\n", + "This notebook explains how to use MistralAIEmbeddings, which is included in the langchain_mistralai package, to embed texts in langchain." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0ab948fc", + "metadata": {}, + "outputs": [], + "source": [ + "# pip install -U langchain-mistralai" + ] + }, + { + "cell_type": "markdown", + "id": "67c637ca", + "metadata": {}, + "source": [ + "## import the library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5709b030", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_mistralai import MistralAIEmbeddings" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1756b1ba", + "metadata": {}, + "outputs": [], + "source": [ + "embedding = MistralAIEmbeddings(mistral_api_key='your-api-key')" + ] + }, + { + "cell_type": "markdown", + "id": "4a2a098d", + "metadata": {}, + "source": [ + "# Using the Embedding Model\n", + "With `MistralAIEmbeddings`, you can directly use the default model 'mistral-embed', or set a different one if available." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "584b9af5", + "metadata": {}, + "outputs": [], + "source": [ + "embedding.model = 'mistral-embed' # or your preferred model if available" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "be18b873", + "metadata": {}, + "outputs": [], + "source": [ + "res_query = embedding.embed_query(\"The test information\")\n", + "res_document = embedding.embed_documents([\"test1\", \"another test\"])" + ] + } + ], + "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.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/partners/mistralai/langchain_mistralai/__init__.py b/libs/partners/mistralai/langchain_mistralai/__init__.py index d592f9da7e4a8..10a26e83fae03 100644 --- a/libs/partners/mistralai/langchain_mistralai/__init__.py +++ b/libs/partners/mistralai/langchain_mistralai/__init__.py @@ -1,3 +1,4 @@ from langchain_mistralai.chat_models import ChatMistralAI +from langchain_mistralai.embeddings import MistralAIEmbeddings -__all__ = ["ChatMistralAI"] +__all__ = ["ChatMistralAI", "MistralAIEmbeddings"] diff --git a/libs/partners/mistralai/langchain_mistralai/chat_models.py b/libs/partners/mistralai/langchain_mistralai/chat_models.py index dda70525d80ec..a13308e5d581c 100644 --- a/libs/partners/mistralai/langchain_mistralai/chat_models.py +++ b/libs/partners/mistralai/langchain_mistralai/chat_models.py @@ -42,27 +42,25 @@ ChatGenerationChunk, ChatResult, ) -from langchain_core.pydantic_v1 import SecretStr, root_validator +from langchain_core.pydantic_v1 import Field, SecretStr, root_validator from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env - -# TODO: Remove 'type: ignore' once mistralai has stubs or py.typed marker. -from mistralai.async_client import MistralAsyncClient # type: ignore[import] -from mistralai.client import MistralClient # type: ignore[import] -from mistralai.constants import ( # type: ignore[import] +from mistralai.async_client import MistralAsyncClient +from mistralai.client import MistralClient +from mistralai.constants import ( ENDPOINT as DEFAULT_MISTRAL_ENDPOINT, ) -from mistralai.exceptions import ( # type: ignore[import] +from mistralai.exceptions import ( MistralAPIException, MistralConnectionException, MistralException, ) -from mistralai.models.chat_completion import ( # type: ignore[import] +from mistralai.models.chat_completion import ( ChatCompletionResponse as MistralChatCompletionResponse, ) -from mistralai.models.chat_completion import ( # type: ignore[import] +from mistralai.models.chat_completion import ( ChatMessage as MistralChatMessage, ) -from mistralai.models.chat_completion import ( # type: ignore[import] +from mistralai.models.chat_completion import ( DeltaMessage as MistralDeltaMessage, ) @@ -156,8 +154,8 @@ def _convert_message_to_mistral_chat_message( class ChatMistralAI(BaseChatModel): """A chat model that uses the MistralAI API.""" - client: MistralClient = None #: :meta private: - async_client: MistralAsyncClient = None #: :meta private: + client: MistralClient = Field(default=None) #: :meta private: + async_client: MistralAsyncClient = Field(default=None) #: :meta private: mistral_api_key: Optional[SecretStr] = None endpoint: str = DEFAULT_MISTRAL_ENDPOINT max_retries: int = 5 diff --git a/libs/partners/mistralai/langchain_mistralai/embeddings.py b/libs/partners/mistralai/langchain_mistralai/embeddings.py new file mode 100644 index 0000000000000..de9c440626216 --- /dev/null +++ b/libs/partners/mistralai/langchain_mistralai/embeddings.py @@ -0,0 +1,141 @@ +import logging +from typing import Dict, List, Optional + +from langchain_core.embeddings import Embeddings +from langchain_core.pydantic_v1 import ( + BaseModel, + Extra, + Field, + SecretStr, + root_validator, +) +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env +from mistralai.async_client import MistralAsyncClient +from mistralai.client import MistralClient +from mistralai.constants import ( + ENDPOINT as DEFAULT_MISTRAL_ENDPOINT, +) +from mistralai.exceptions import MistralException + +logger = logging.getLogger(__name__) + + +class MistralAIEmbeddings(BaseModel, Embeddings): + """MistralAI embedding models. + + To use, set the environment variable `MISTRAL_API_KEY` is set with your API key or + pass it as a named parameter to the constructor. + + Example: + .. code-block:: python + + from langchain_mistralai import MistralAIEmbeddings + mistral = MistralAIEmbeddings( + model="mistral-embed", + mistral_api_key="my-api-key" + ) + """ + + client: MistralClient = Field(default=None) #: :meta private: + async_client: MistralAsyncClient = Field(default=None) #: :meta private: + mistral_api_key: Optional[SecretStr] = None + endpoint: str = DEFAULT_MISTRAL_ENDPOINT + max_retries: int = 5 + timeout: int = 120 + max_concurrent_requests: int = 64 + + model: str = "mistral-embed" + + class Config: + extra = Extra.forbid + arbitrary_types_allowed = True + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + """Validate configuration.""" + + values["mistral_api_key"] = convert_to_secret_str( + get_from_dict_or_env( + values, "mistral_api_key", "MISTRAL_API_KEY", default="" + ) + ) + values["client"] = MistralClient( + api_key=values["mistral_api_key"].get_secret_value(), + endpoint=values["endpoint"], + max_retries=values["max_retries"], + timeout=values["timeout"], + ) + values["async_client"] = MistralAsyncClient( + api_key=values["mistral_api_key"].get_secret_value(), + endpoint=values["endpoint"], + max_retries=values["max_retries"], + timeout=values["timeout"], + max_concurrent_requests=values["max_concurrent_requests"], + ) + return values + + def embed_documents(self, texts: List[str]) -> List[List[float]]: + """Embed a list of document texts. + + Args: + texts: The list of texts to embed. + + Returns: + List of embeddings, one for each text. + """ + try: + embeddings_batch_response = self.client.embeddings( + model=self.model, + input=texts, + ) + return [ + list(map(float, embedding_obj.embedding)) + for embedding_obj in embeddings_batch_response.data + ] + except MistralException as e: + logger.error(f"An error occurred with MistralAI: {e}") + raise + + async def aembed_documents(self, texts: List[str]) -> List[List[float]]: + """Embed a list of document texts. + + Args: + texts: The list of texts to embed. + + Returns: + List of embeddings, one for each text. + """ + try: + embeddings_batch_response = await self.async_client.embeddings( + model=self.model, + input=texts, + ) + return [ + list(map(float, embedding_obj.embedding)) + for embedding_obj in embeddings_batch_response.data + ] + except MistralException as e: + logger.error(f"An error occurred with MistralAI: {e}") + raise + + def embed_query(self, text: str) -> List[float]: + """Embed a single query text. + + Args: + text: The text to embed. + + Returns: + Embedding for the text. + """ + return self.embed_documents([text])[0] + + async def aembed_query(self, text: str) -> List[float]: + """Embed a single query text. + + Args: + text: The text to embed. + + Returns: + Embedding for the text. + """ + return (await self.aembed_documents([text]))[0] diff --git a/libs/partners/mistralai/poetry.lock b/libs/partners/mistralai/poetry.lock index d7aefe11e703b..8d406846bf2bf 100644 --- a/libs/partners/mistralai/poetry.lock +++ b/libs/partners/mistralai/poetry.lock @@ -1,115 +1,5 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. -[[package]] -name = "aiohttp" -version = "3.9.1" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, -] - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "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.6.0" @@ -145,46 +35,6 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin 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", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] -[[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 = "23.1.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] - -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - [[package]] name = "certifi" version = "2023.11.17" @@ -338,91 +188,61 @@ files = [ test = ["pytest (>=6)"] [[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" +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.2" +description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, ] +[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,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.25.2" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.25.2-py3-none-any.whl", hash = "sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118"}, + {file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"}, +] + +[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.*)"] + [[package]] name = "idna" version = "3.6" @@ -513,104 +333,19 @@ requests = ">=2,<3" [[package]] name = "mistralai" -version = "0.0.8" +version = "0.0.11" description = "" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "mistralai-0.0.8-py3-none-any.whl", hash = "sha256:288d31c30d40aacef46c98f05676813153938e36e1a3d49c3943eba227faf8b3"}, - {file = "mistralai-0.0.8.tar.gz", hash = "sha256:c1d9f53f75d6b99f614ce3d08cf90d99927c1af73ec986859ebe058431a18a5b"}, + {file = "mistralai-0.0.11-py3-none-any.whl", hash = "sha256:fb2a240a3985420c4e7db48eb5077d6d6dbc5e83cac0dd948c20342fb48087ee"}, + {file = "mistralai-0.0.11.tar.gz", hash = "sha256:383072715531198305dab829ab3749b64933bbc2549354f3c9ebc43c17b912cf"}, ] [package.dependencies] -aiohttp = ">=3.9.1,<4.0.0" -backoff = ">=2.2.1,<3.0.0" +httpx = ">=0.25.2,<0.26.0" orjson = ">=3.9.10,<4.0.0" pydantic = ">=2.5.2,<3.0.0" -requests = ">=2.31.0,<3.0.0" - -[[package]] -name = "multidict" -version = "6.0.4" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] [[package]] name = "mypy" @@ -1093,110 +828,7 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "yarl" -version = "1.9.4" -description = "Yet another URL library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "7773b2b3f8241dfbeff01c93e9ea16daabaa287ab4f11fdc7a1f673d476b2930" +content-hash = "72f02f84025d4cda9edb7f7105aecf65cc6341541143c45e5f2885c30aea5a0d" diff --git a/libs/partners/mistralai/pyproject.toml b/libs/partners/mistralai/pyproject.toml index 5ea94e070f330..807c8c445b595 100644 --- a/libs/partners/mistralai/pyproject.toml +++ b/libs/partners/mistralai/pyproject.toml @@ -12,7 +12,7 @@ repository = "https://github.com/langchain-ai/langchain" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" langchain-core = "^0.1" -mistralai = "^0.0.8" +mistralai = "^0.0.11" [tool.poetry.group.test] optional = true diff --git a/libs/partners/mistralai/tests/integration_tests/test_embeddings.py b/libs/partners/mistralai/tests/integration_tests/test_embeddings.py new file mode 100644 index 0000000000000..b179f64d80f14 --- /dev/null +++ b/libs/partners/mistralai/tests/integration_tests/test_embeddings.py @@ -0,0 +1,19 @@ +"""Test MistralAI Embedding""" +from langchain_mistralai import MistralAIEmbeddings + + +def test_mistralai_embedding_documents() -> None: + """Test MistralAI embeddings for documents.""" + documents = ["foo bar", "test document"] + embedding = MistralAIEmbeddings() + output = embedding.embed_documents(documents) + assert len(output) == 2 + assert len(output[0]) == 1024 + + +def test_mistralai_embedding_query() -> None: + """Test MistralAI embeddings for query.""" + document = "foo bar" + embedding = MistralAIEmbeddings() + output = embedding.embed_query(document) + assert len(output) == 1024 diff --git a/libs/partners/mistralai/tests/unit_tests/test_embeddings.py b/libs/partners/mistralai/tests/unit_tests/test_embeddings.py new file mode 100644 index 0000000000000..14055af4ed7d5 --- /dev/null +++ b/libs/partners/mistralai/tests/unit_tests/test_embeddings.py @@ -0,0 +1,10 @@ +import os + +from langchain_mistralai import MistralAIEmbeddings + +os.environ["MISTRAL_API_KEY"] = "foo" + + +def test_mistral_init() -> None: + embeddings = MistralAIEmbeddings() + assert embeddings.model == "mistral-embed" diff --git a/libs/partners/mistralai/tests/unit_tests/test_imports.py b/libs/partners/mistralai/tests/unit_tests/test_imports.py index d1caf29c067ae..01c220d64c9d2 100644 --- a/libs/partners/mistralai/tests/unit_tests/test_imports.py +++ b/libs/partners/mistralai/tests/unit_tests/test_imports.py @@ -1,6 +1,6 @@ from langchain_mistralai import __all__ -EXPECTED_ALL = ["ChatMistralAI"] +EXPECTED_ALL = ["ChatMistralAI", "MistralAIEmbeddings"] def test_all_imports() -> None: From f3601b0aaff4a7e9a0945a4e7bc784e44f84a481 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:00:55 -0800 Subject: [PATCH 046/215] Community[Patch] Remove docs form bm25 repr (#16110) Resolves: https://github.com/langchain-ai/langsmith-sdk/issues/356 --- libs/community/langchain_community/retrievers/bm25.py | 3 ++- .../tests/unit_tests/retrievers/test_bm25.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/retrievers/bm25.py b/libs/community/langchain_community/retrievers/bm25.py index c0e0b248313fa..0ebaa2c0cd285 100644 --- a/libs/community/langchain_community/retrievers/bm25.py +++ b/libs/community/langchain_community/retrievers/bm25.py @@ -4,6 +4,7 @@ from langchain_core.callbacks import CallbackManagerForRetrieverRun from langchain_core.documents import Document +from langchain_core.pydantic_v1 import Field from langchain_core.retrievers import BaseRetriever @@ -16,7 +17,7 @@ class BM25Retriever(BaseRetriever): vectorizer: Any """ BM25 vectorizer.""" - docs: List[Document] + docs: List[Document] = Field(repr=False) """ List of documents.""" k: int = 4 """ Number of documents to return.""" diff --git a/libs/community/tests/unit_tests/retrievers/test_bm25.py b/libs/community/tests/unit_tests/retrievers/test_bm25.py index d36f6dae15ab1..ef40b25ba7dee 100644 --- a/libs/community/tests/unit_tests/retrievers/test_bm25.py +++ b/libs/community/tests/unit_tests/retrievers/test_bm25.py @@ -32,3 +32,14 @@ def test_from_documents() -> None: bm25_retriever = BM25Retriever.from_documents(documents=input_docs) assert len(bm25_retriever.docs) == 3 assert bm25_retriever.vectorizer.doc_len == [4, 5, 4] + + +@pytest.mark.requires("rank_bm25") +def test_repr() -> None: + input_docs = [ + Document(page_content="I have a pen."), + Document(page_content="Do you have a pen?"), + Document(page_content="I have a bag."), + ] + bm25_retriever = BM25Retriever.from_documents(documents=input_docs) + assert "I have a pen" not in repr(bm25_retriever) From e5cf1e2414a22c7465f0f47bd661ece183e88924 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:30:07 -0800 Subject: [PATCH 047/215] Community[patch]use secret str in Tavily and HuggingFaceInferenceEmbeddings (#16109) So the api keys don't show up in repr's Still need to do tests --- .../langchain_community/embeddings/huggingface.py | 6 +++--- .../langchain_community/utilities/tavily_search.py | 8 ++++---- .../tests/unit_tests/embeddings/test_huggingface.py | 7 +++++++ libs/community/tests/unit_tests/utilities/test_tavily.py | 7 +++++++ libs/langchain/langchain/smith/evaluation/runner_utils.py | 1 + 5 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 libs/community/tests/unit_tests/embeddings/test_huggingface.py create mode 100644 libs/community/tests/unit_tests/utilities/test_tavily.py diff --git a/libs/community/langchain_community/embeddings/huggingface.py b/libs/community/langchain_community/embeddings/huggingface.py index 84a568866f178..068aafa1fe1db 100644 --- a/libs/community/langchain_community/embeddings/huggingface.py +++ b/libs/community/langchain_community/embeddings/huggingface.py @@ -2,7 +2,7 @@ import requests from langchain_core.embeddings import Embeddings -from langchain_core.pydantic_v1 import BaseModel, Extra, Field +from langchain_core.pydantic_v1 import BaseModel, Extra, Field, SecretStr DEFAULT_MODEL_NAME = "sentence-transformers/all-mpnet-base-v2" DEFAULT_INSTRUCT_MODEL = "hkunlp/instructor-large" @@ -275,7 +275,7 @@ class HuggingFaceInferenceAPIEmbeddings(BaseModel, Embeddings): Requires a HuggingFace Inference API key and a model name. """ - api_key: str + api_key: SecretStr """Your API key for the HuggingFace Inference API.""" model_name: str = "sentence-transformers/all-MiniLM-L6-v2" """The name of the model to use for text embeddings.""" @@ -297,7 +297,7 @@ def _default_api_url(self) -> str: @property def _headers(self) -> dict: - return {"Authorization": f"Bearer {self.api_key}"} + return {"Authorization": f"Bearer {self.api_key.get_secret_value()}"} def embed_documents(self, texts: List[str]) -> List[List[float]]: """Get the embeddings for a list of texts. diff --git a/libs/community/langchain_community/utilities/tavily_search.py b/libs/community/langchain_community/utilities/tavily_search.py index 54cd0810cc265..97dc45363f2ee 100644 --- a/libs/community/langchain_community/utilities/tavily_search.py +++ b/libs/community/langchain_community/utilities/tavily_search.py @@ -7,7 +7,7 @@ import aiohttp import requests -from langchain_core.pydantic_v1 import BaseModel, Extra, root_validator +from langchain_core.pydantic_v1 import BaseModel, Extra, SecretStr, root_validator from langchain_core.utils import get_from_dict_or_env TAVILY_API_URL = "https://api.tavily.com" @@ -16,7 +16,7 @@ class TavilySearchAPIWrapper(BaseModel): """Wrapper for Tavily Search API.""" - tavily_api_key: str + tavily_api_key: SecretStr class Config: """Configuration for this pydantic object.""" @@ -45,7 +45,7 @@ def raw_results( include_images: Optional[bool] = False, ) -> Dict: params = { - "api_key": self.tavily_api_key, + "api_key": self.tavily_api_key.get_secret_value(), "query": query, "max_results": max_results, "search_depth": search_depth, @@ -126,7 +126,7 @@ async def raw_results_async( # Function to perform the API call async def fetch() -> str: params = { - "api_key": self.tavily_api_key, + "api_key": self.tavily_api_key.get_secret_value(), "query": query, "max_results": max_results, "search_depth": search_depth, diff --git a/libs/community/tests/unit_tests/embeddings/test_huggingface.py b/libs/community/tests/unit_tests/embeddings/test_huggingface.py new file mode 100644 index 0000000000000..22f91acb8aaf6 --- /dev/null +++ b/libs/community/tests/unit_tests/embeddings/test_huggingface.py @@ -0,0 +1,7 @@ +from langchain_community.embeddings.huggingface import HuggingFaceInferenceAPIEmbeddings + + +def test_hugginggface_inferenceapi_embedding_documents_init() -> None: + """Test huggingface embeddings.""" + embedding = HuggingFaceInferenceAPIEmbeddings(api_key="abcd123") + assert "abcd123" not in repr(embedding) diff --git a/libs/community/tests/unit_tests/utilities/test_tavily.py b/libs/community/tests/unit_tests/utilities/test_tavily.py new file mode 100644 index 0000000000000..751bca0ccadfd --- /dev/null +++ b/libs/community/tests/unit_tests/utilities/test_tavily.py @@ -0,0 +1,7 @@ +from langchain_community.utilities.tavily_search import TavilySearchAPIWrapper + + +def test_api_wrapper_api_key_not_visible() -> None: + """Test that an exception is raised if the API key is not present.""" + wrapper = TavilySearchAPIWrapper(tavily_api_key="abcd123") + assert "abcd123" not in repr(wrapper) diff --git a/libs/langchain/langchain/smith/evaluation/runner_utils.py b/libs/langchain/langchain/smith/evaluation/runner_utils.py index 6a98e4a73e14e..c79b0fc4c84fb 100644 --- a/libs/langchain/langchain/smith/evaluation/runner_utils.py +++ b/libs/langchain/langchain/smith/evaluation/runner_utils.py @@ -97,6 +97,7 @@ def get_aggregate_feedback( for col in df.columns if col.startswith("inputs.") or col.startswith("outputs.") + or col in {"input", "output"} or col.startswith("reference") ] return df.describe(include="all").drop(to_drop, axis=1) From ce10fe0c2f6f044f6a420416f4e2c2df80c80d60 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Wed, 17 Jan 2024 08:36:05 -0800 Subject: [PATCH 048/215] mistralai[patch]: release 0.0.3 (#16116) embeddings --- libs/partners/mistralai/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/partners/mistralai/pyproject.toml b/libs/partners/mistralai/pyproject.toml index 807c8c445b595..79cba780b5966 100644 --- a/libs/partners/mistralai/pyproject.toml +++ b/libs/partners/mistralai/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-mistralai" -version = "0.0.2.post1" +version = "0.0.3" description = "An integration package connecting Mistral and LangChain" authors = [] readme = "README.md" From 06fe2f4fb02cb3244bd61f86f25bc163e9800287 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Wed, 17 Jan 2024 08:37:13 -0800 Subject: [PATCH 049/215] partners: add license field (#16117) - bumps package post versions for packages without current unreleased updates - will bump package version in release prs associated with packages that do have changes (mistral, vertex) --- libs/partners/anthropic/pyproject.toml | 3 ++- libs/partners/google-genai/pyproject.toml | 3 ++- libs/partners/google-vertexai/pyproject.toml | 1 + libs/partners/mistralai/pyproject.toml | 1 + libs/partners/nvidia-ai-endpoints/pyproject.toml | 3 ++- libs/partners/nvidia-trt/pyproject.toml | 1 + libs/partners/openai/pyproject.toml | 3 ++- libs/partners/robocorp/pyproject.toml | 3 ++- libs/partners/together/pyproject.toml | 3 ++- 9 files changed, 15 insertions(+), 6 deletions(-) diff --git a/libs/partners/anthropic/pyproject.toml b/libs/partners/anthropic/pyproject.toml index 53ab7dd79fd70..459d07d3b4ff3 100644 --- a/libs/partners/anthropic/pyproject.toml +++ b/libs/partners/anthropic/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] name = "langchain-anthropic" -version = "0.0.1.post1" +version = "0.0.1.post2" description = "An integration package connecting AnthropicMessages and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/anthropic" diff --git a/libs/partners/google-genai/pyproject.toml b/libs/partners/google-genai/pyproject.toml index f98ed0a765edb..28884a3cbf13f 100644 --- a/libs/partners/google-genai/pyproject.toml +++ b/libs/partners/google-genai/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] name = "langchain-google-genai" -version = "0.0.6" +version = "0.0.6.post1" description = "An integration package connecting Google's genai package and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/google-genai" diff --git a/libs/partners/google-vertexai/pyproject.toml b/libs/partners/google-vertexai/pyproject.toml index 34c2be261ddda..e1ce09d46cd4d 100644 --- a/libs/partners/google-vertexai/pyproject.toml +++ b/libs/partners/google-vertexai/pyproject.toml @@ -5,6 +5,7 @@ description = "An integration package connecting GoogleVertexAI and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/google-vertexai" diff --git a/libs/partners/mistralai/pyproject.toml b/libs/partners/mistralai/pyproject.toml index 79cba780b5966..75c87ec329d26 100644 --- a/libs/partners/mistralai/pyproject.toml +++ b/libs/partners/mistralai/pyproject.toml @@ -5,6 +5,7 @@ description = "An integration package connecting Mistral and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/mistralai" diff --git a/libs/partners/nvidia-ai-endpoints/pyproject.toml b/libs/partners/nvidia-ai-endpoints/pyproject.toml index 6ba0e0ca90cd5..0146a6f75f521 100644 --- a/libs/partners/nvidia-ai-endpoints/pyproject.toml +++ b/libs/partners/nvidia-ai-endpoints/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] name = "langchain-nvidia-ai-endpoints" -version = "0.0.1.post1" +version = "0.0.1.post2" description = "An integration package connecting NVIDIA AI Endpoints and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/nvidia-ai-endpoints" diff --git a/libs/partners/nvidia-trt/pyproject.toml b/libs/partners/nvidia-trt/pyproject.toml index a8847942f7648..2cbaaa6be1403 100644 --- a/libs/partners/nvidia-trt/pyproject.toml +++ b/libs/partners/nvidia-trt/pyproject.toml @@ -5,6 +5,7 @@ description = "An integration package connecting TritonTensorRT and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/nvidia-trt" diff --git a/libs/partners/openai/pyproject.toml b/libs/partners/openai/pyproject.toml index 3cb369ca2eda9..6bdf5a1f62be3 100644 --- a/libs/partners/openai/pyproject.toml +++ b/libs/partners/openai/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] name = "langchain-openai" -version = "0.0.2.post1" +version = "0.0.2.post2" description = "An integration package connecting OpenAI and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/openai" diff --git a/libs/partners/robocorp/pyproject.toml b/libs/partners/robocorp/pyproject.toml index c80e5d81ed51b..e8e7974146b09 100644 --- a/libs/partners/robocorp/pyproject.toml +++ b/libs/partners/robocorp/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] name = "langchain-robocorp" -version = "0.0.1.post2" +version = "0.0.1.post3" description = "An integration package connecting Robocorp and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/robocorp" diff --git a/libs/partners/together/pyproject.toml b/libs/partners/together/pyproject.toml index 1193130016117..6bf4795ce1437 100644 --- a/libs/partners/together/pyproject.toml +++ b/libs/partners/together/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] name = "langchain-together" -version = "0.0.2.post1" +version = "0.0.2.post2" description = "An integration package connecting Together and LangChain" authors = [] readme = "README.md" repository = "https://github.com/langchain-ai/langchain" +license = "MIT" [tool.poetry.urls] "Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/together" From a35e5f19a803d314ff2ff6b85ad96bbb1d0712a4 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine <eltociear@gmail.com> Date: Thu, 18 Jan 2024 01:48:24 +0900 Subject: [PATCH 050/215] docs: Update gradient.ipynb (#16149) Enviroment -> Environment --- docs/docs/integrations/llms/gradient.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/llms/gradient.ipynb b/docs/docs/integrations/llms/gradient.ipynb index 9dff7f01e1c00..8d46fa089684c 100644 --- a/docs/docs/integrations/llms/gradient.ipynb +++ b/docs/docs/integrations/llms/gradient.ipynb @@ -59,7 +59,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Optional: Validate your Enviroment variables ```GRADIENT_ACCESS_TOKEN``` and ```GRADIENT_WORKSPACE_ID``` to get currently deployed models. Using the `gradientai` Python package." + "Optional: Validate your Environment variables ```GRADIENT_ACCESS_TOKEN``` and ```GRADIENT_WORKSPACE_ID``` to get currently deployed models. Using the `gradientai` Python package." ] }, { From 3606c5d5e93877c8bd7f7662675cb76e77ef301c Mon Sep 17 00:00:00 2001 From: purificant <purificant@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:51:20 +0000 Subject: [PATCH 051/215] infra: update poetry 1.6.1 -> 1.7.1 (#15027) --- .github/workflows/_all_ci.yml | 2 +- .github/workflows/_compile_integration_test.yml | 2 +- .github/workflows/_dependencies.yml | 2 +- .github/workflows/_integration_test.yml | 2 +- .github/workflows/_lint.yml | 2 +- .github/workflows/_release.yml | 2 +- .github/workflows/_test.yml | 2 +- .github/workflows/_test_release.yml | 2 +- .github/workflows/scheduled_test.yml | 2 +- .github/workflows/templates_ci.yml | 2 +- docs/docs/contributing/code.mdx | 4 ++-- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/_all_ci.yml b/.github/workflows/_all_ci.yml index 7be66470dd195..a2c4e06d6c15d 100644 --- a/.github/workflows/_all_ci.yml +++ b/.github/workflows/_all_ci.yml @@ -32,7 +32,7 @@ concurrency: cancel-in-progress: true env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: lint: diff --git a/.github/workflows/_compile_integration_test.yml b/.github/workflows/_compile_integration_test.yml index 66c587f1251c3..12cd3d737b2d7 100644 --- a/.github/workflows/_compile_integration_test.yml +++ b/.github/workflows/_compile_integration_test.yml @@ -9,7 +9,7 @@ on: description: "From which folder this pipeline executes" env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: build: diff --git a/.github/workflows/_dependencies.yml b/.github/workflows/_dependencies.yml index af01a7eafa77d..1414f63472fa0 100644 --- a/.github/workflows/_dependencies.yml +++ b/.github/workflows/_dependencies.yml @@ -13,7 +13,7 @@ on: description: "Relative path to the langchain library folder" env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: build: diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index e6c8296c59c19..030883e555827 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -8,7 +8,7 @@ on: type: string env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: build: diff --git a/.github/workflows/_lint.yml b/.github/workflows/_lint.yml index e34305aa0bc54..07978a25cf258 100644 --- a/.github/workflows/_lint.yml +++ b/.github/workflows/_lint.yml @@ -13,7 +13,7 @@ on: description: "Relative path to the langchain library folder" env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" WORKDIR: ${{ inputs.working-directory == '' && '.' || inputs.working-directory }} # This env var allows us to get inline annotations when ruff has complaints. diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index ad85a3a25639e..5de6107520850 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -16,7 +16,7 @@ on: env: PYTHON_VERSION: "3.10" - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: build: diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 2ae665f536c2d..55c5f79fcbcf1 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -13,7 +13,7 @@ on: description: "Relative path to the langchain library folder" env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: build: diff --git a/.github/workflows/_test_release.yml b/.github/workflows/_test_release.yml index 0fc25a751644c..035158bf2034f 100644 --- a/.github/workflows/_test_release.yml +++ b/.github/workflows/_test_release.yml @@ -9,7 +9,7 @@ on: description: "From which folder this pipeline executes" env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" PYTHON_VERSION: "3.10" jobs: diff --git a/.github/workflows/scheduled_test.yml b/.github/workflows/scheduled_test.yml index 0130f427d92b8..4ae8b755c146c 100644 --- a/.github/workflows/scheduled_test.yml +++ b/.github/workflows/scheduled_test.yml @@ -6,7 +6,7 @@ on: - cron: '0 13 * * *' env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" jobs: build: diff --git a/.github/workflows/templates_ci.yml b/.github/workflows/templates_ci.yml index a4c83f326cbdc..b886fc7aaff81 100644 --- a/.github/workflows/templates_ci.yml +++ b/.github/workflows/templates_ci.yml @@ -24,7 +24,7 @@ concurrency: cancel-in-progress: true env: - POETRY_VERSION: "1.6.1" + POETRY_VERSION: "1.7.1" WORKDIR: "templates" jobs: diff --git a/docs/docs/contributing/code.mdx b/docs/docs/contributing/code.mdx index 279bdc05303cc..d3f957d1f8902 100644 --- a/docs/docs/contributing/code.mdx +++ b/docs/docs/contributing/code.mdx @@ -32,7 +32,7 @@ For a [development container](https://containers.dev/), see the [.devcontainer f ### Dependency Management: Poetry and other env/dependency managers -This project utilizes [Poetry](https://python-poetry.org/) v1.6.1+ as a dependency manager. +This project utilizes [Poetry](https://python-poetry.org/) v1.7.1+ as a dependency manager. ❗Note: *Before installing Poetry*, if you use `Conda`, create and activate a new Conda env (e.g. `conda create -n langchain python=3.9`) @@ -75,7 +75,7 @@ make test If during installation you receive a `WheelFileValidationError` for `debugpy`, please make sure you are running Poetry v1.6.1+. This bug was present in older versions of Poetry (e.g. 1.4.1) and has been resolved in newer releases. -If you are still seeing this bug on v1.6.1, you may also try disabling "modern installation" +If you are still seeing this bug on v1.6.1+, you may also try disabling "modern installation" (`poetry config installer.modern-installation false`) and re-installing requirements. See [this `debugpy` issue](https://github.com/microsoft/debugpy/issues/1246) for more details. From d91126fc64ebd5d28bbd73ca4d15a9daea45c8a2 Mon Sep 17 00:00:00 2001 From: Felix Krones <92753725+felixkrones@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:10:43 +0100 Subject: [PATCH 052/215] community[patch]: missing unpack operator for or_clause in pgvector document filter (#16148) - Fix for #16146 - Adding unpack operation to "or" and "and" filter for pgvector retriever. # --- libs/community/langchain_community/vectorstores/pgvector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/pgvector.py b/libs/community/langchain_community/vectorstores/pgvector.py index e57fb16358c8e..7fed642c45c95 100644 --- a/libs/community/langchain_community/vectorstores/pgvector.py +++ b/libs/community/langchain_community/vectorstores/pgvector.py @@ -545,13 +545,13 @@ def _create_filter_clause(self, key, value): self._create_filter_clause(key, sub_value) for sub_value in value_case_insensitive[OR] ] - filter_by_metadata = sqlalchemy.or_(or_clauses) + filter_by_metadata = sqlalchemy.or_(*or_clauses) elif AND in map(str.lower, value): and_clauses = [ self._create_filter_clause(key, sub_value) for sub_value in value_case_insensitive[AND] ] - filter_by_metadata = sqlalchemy.and_(and_clauses) + filter_by_metadata = sqlalchemy.and_(*and_clauses) else: filter_by_metadata = None From b0c3e3db2b24dd010d2994bb2bd3853aa02421ba Mon Sep 17 00:00:00 2001 From: BeatrixCohere <128378696+BeatrixCohere@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:11:00 +0000 Subject: [PATCH 053/215] community[patch]: Handle when documents are not provided in the Cohere response (#16144) - **Description:** This handles the cohere response when documents aren't included in the response - **Issue:** N/A - **Dependencies:** N/A - **Twitter handle:** N/A --- .../retrievers/cohere_rag_retriever.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/community/langchain_community/retrievers/cohere_rag_retriever.py b/libs/community/langchain_community/retrievers/cohere_rag_retriever.py index 39dcc30f3b451..91f4c3a0886f5 100644 --- a/libs/community/langchain_community/retrievers/cohere_rag_retriever.py +++ b/libs/community/langchain_community/retrievers/cohere_rag_retriever.py @@ -17,10 +17,14 @@ def _get_docs(response: Any) -> List[Document]: - docs = [ - Document(page_content=doc["snippet"], metadata=doc) - for doc in response.generation_info["documents"] - ] + docs = ( + [] + if "documents" not in response.generation_info + else [ + Document(page_content=doc["snippet"], metadata=doc) + for doc in response.generation_info["documents"] + ] + ) docs.append( Document( page_content=response.message.content, From da96c511d1bcf6175d797e64bfcf90649743a93d Mon Sep 17 00:00:00 2001 From: Abhinav <60320192+blacksmithop@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:41:16 +0530 Subject: [PATCH 054/215] docs: Replace azure_cosmos_db_vector_search with azure_cosmos_db in Cosmos DB Documentation (#16122) **Description**: This PR fixes an error in the documentation for Azure Cosmos DB Integration. **Issue**: The correct way to import `AzureCosmosDBVectorSearch` is ```python from langchain_community.vectorstores.azure_cosmos_db import ( AzureCosmosDBVectorSearch, ) ``` While the [documentation](https://python.langchain.com/docs/integrations/vectorstores/azure_cosmos_db) states it to be ```python from langchain_community.vectorstores.azure_cosmos_db_vector_search import ( AzureCosmosDBVectorSearch, CosmosDBSimilarityType, ) ``` As you can see in [azure_cosmos_db.py](https://github.com/langchain-ai/langchain/blob/c323742f4fda30e8bef5381f41f66192afb2b9d2/libs/langchain/langchain/vectorstores/azure_cosmos_db.py#L1C45-L2) **Dependencies:**: None **Twitter handle**: None --- docs/docs/integrations/vectorstores/azure_cosmos_db.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/vectorstores/azure_cosmos_db.ipynb b/docs/docs/integrations/vectorstores/azure_cosmos_db.ipynb index b8ac0b5df5c59..9c00fe0d7e111 100644 --- a/docs/docs/integrations/vectorstores/azure_cosmos_db.ipynb +++ b/docs/docs/integrations/vectorstores/azure_cosmos_db.ipynb @@ -132,7 +132,7 @@ "source": [ "from langchain.text_splitter import CharacterTextSplitter\n", "from langchain_community.document_loaders import TextLoader\n", - "from langchain_community.vectorstores.azure_cosmos_db_vector_search import (\n", + "from langchain_community.vectorstores.azure_cosmos_db import (\n", " AzureCosmosDBVectorSearch,\n", " CosmosDBSimilarityType,\n", ")\n", From f406dc387256daecc6a2c8d02727f38ad2f2c1e1 Mon Sep 17 00:00:00 2001 From: Kapil Sachdeva <ksachdeva17@gmail.com> Date: Wed, 17 Jan 2024 11:11:27 -0600 Subject: [PATCH 055/215] docs: in RunnableRetry, correct the example snippet that uses with_retry method on Runnable (#16108) The example code snippet for with_retry is using incorrect argument names. This PR fixes that --- libs/core/langchain_core/runnables/retry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/core/langchain_core/runnables/retry.py b/libs/core/langchain_core/runnables/retry.py index f619c45f592fc..36a508776ce62 100644 --- a/libs/core/langchain_core/runnables/retry.py +++ b/libs/core/langchain_core/runnables/retry.py @@ -56,14 +56,14 @@ class RunnableRetry(RunnableBindingBase[Input, Output]): def foo(input) -> None: '''Fake function that raises an exception.''' - raise ValueError("Invoking foo failed. At time {time.time()}") + raise ValueError(f"Invoking foo failed. At time {time.time()}") runnable = RunnableLambda(foo) runnable_with_retries = runnable.with_retry( - retry_exception_types=(ValueError,), # Retry only on ValueError + retry_if_exception_type=(ValueError,), # Retry only on ValueError wait_exponential_jitter=True, # Add jitter to the exponential backoff - max_attempt_number=2, # Try twice + stop_after_attempt=2, # Try twice ) # The method invocation above is equivalent to the longer form below: From 9c2f1f07a002a480b45c5318d581662edbf38b18 Mon Sep 17 00:00:00 2001 From: David DeCaprio <daved@alum.mit.edu> Date: Wed, 17 Jan 2024 11:39:44 -0600 Subject: [PATCH 056/215] docs: Updated SQLite example to use LCEL and SQLChatMessageHistory (#16094) - **Description:** Updated the SQLite example integration notebook to latest standards - **Issue:** [15664](https://github.com/langchain-ai/langchain/issues/15664) - **Dependencies:** None - **Twitter handle:** @davedecaprio --- docs/docs/integrations/memory/sqlite.ipynb | 231 ++++++++++++--------- 1 file changed, 131 insertions(+), 100 deletions(-) diff --git a/docs/docs/integrations/memory/sqlite.ipynb b/docs/docs/integrations/memory/sqlite.ipynb index d25dbeefffe26..200335cc4df4e 100644 --- a/docs/docs/integrations/memory/sqlite.ipynb +++ b/docs/docs/integrations/memory/sqlite.ipynb @@ -16,172 +16,203 @@ }, { "cell_type": "code", - "execution_count": 1, - "id": "d0a07a30-028f-4e16-8b11-45b2416f7b0f", + "execution_count": null, + "id": "5c923f56-24a9-4f8f-9b91-138cc025c47e", "metadata": {}, "outputs": [], "source": [ - "%pip install --upgrade --quiet sqlite3" + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" ] }, { - "cell_type": "code", - "execution_count": 1, - "id": "db59b901", - "metadata": { - "id": "2wUMSUoF8ffn" - }, - "outputs": [], + "cell_type": "markdown", + "id": "61fda020-23a2-4605-afad-58260535ec8c", + "metadata": {}, "source": [ - "from langchain.chains import ConversationChain\n", - "from langchain.memory import ConversationEntityMemory\n", - "from langchain.memory.entity import SQLiteEntityStore\n", - "from langchain.memory.prompt import ENTITY_MEMORY_CONVERSATION_TEMPLATE\n", - "from langchain_openai import OpenAI" + "## Usage\n", + "\n", + "To use the storage you need to provide only 2 things:\n", + "\n", + "1. Session Id - a unique identifier of the session, like user name, email, chat id etc.\n", + "2. Connection string - a string that specifies the database connection. For SQLite, that string is `slqlite:///` followed by the name of the database file. If that file doesn't exist, it will be created." ] }, { "cell_type": "code", - "execution_count": 2, - "id": "ca6dee29", + "execution_count": 1, + "id": "4576e914a866fb40", "metadata": { - "id": "8TpJZti99gxV" + "ExecuteTime": { + "end_time": "2023-08-28T10:04:38.077748Z", + "start_time": "2023-08-28T10:04:36.105894Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ - "entity_store = SQLiteEntityStore()\n", - "llm = OpenAI(temperature=0)\n", - "memory = ConversationEntityMemory(llm=llm, entity_store=entity_store)\n", - "conversation = ConversationChain(\n", - " llm=llm,\n", - " prompt=ENTITY_MEMORY_CONVERSATION_TEMPLATE,\n", - " memory=memory,\n", - " verbose=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "f9b4c3a0", - "metadata": { - "id": "HEAHG1L79ca1" - }, - "source": [ - "Notice the usage of `EntitySqliteStore` as parameter to `entity_store` on the `memory` property." + "from langchain_community.chat_message_histories import SQLChatMessageHistory\n", + "\n", + "chat_message_history = SQLChatMessageHistory(\n", + " session_id=\"test_session_id\", connection_string=\"sqlite:///sqlite.db\"\n", + ")\n", + "\n", + "chat_message_history.add_user_message(\"Hello\")\n", + "chat_message_history.add_ai_message(\"Hi\")" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "297e78a6", + "execution_count": 2, + "id": "b476688cbb32ba90", "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 437 + "ExecuteTime": { + "end_time": "2023-08-28T10:04:38.929396Z", + "start_time": "2023-08-28T10:04:38.915727Z" }, - "id": "BzXphJWf_TAZ", - "outputId": "de7fc966-e0fd-4daf-a9bd-4743455ea774" + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n", - "Prompt after formatting:\n", - "\u001b[32;1m\u001b[1;3mYou are an assistant to a human, powered by a large language model trained by OpenAI.\n", - "\n", - "You are designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, you are able to generate human-like text based on the input you receive, allowing you to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n", - "\n", - "You are constantly learning and improving, and your capabilities are constantly evolving. You are able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. You have access to some personalized information provided by the human in the Context section below. Additionally, you are able to generate your own text based on the input you receive, allowing you to engage in discussions and provide explanations and descriptions on a wide range of topics.\n", - "\n", - "Overall, you are a powerful tool that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether the human needs help with a specific question or just wants to have a conversation about a particular topic, you are here to assist.\n", - "\n", - "Context:\n", - "{'Deven': 'Deven is working on a hackathon project with Sam.', 'Sam': 'Sam is working on a hackathon project with Deven.'}\n", - "\n", - "Current conversation:\n", - "\n", - "Last line:\n", - "Human: Deven & Sam are working on a hackathon project\n", - "You:\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, { "data": { "text/plain": [ - "' That sounds like a great project! What kind of project are they working on?'" + "[HumanMessage(content='Hello'), AIMessage(content='Hi')]" ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "conversation.run(\"Deven & Sam are working on a hackathon project\")" + "chat_message_history.messages" + ] + }, + { + "cell_type": "markdown", + "id": "e400509a-1957-4d1d-bbd6-01e8dc3dccb3", + "metadata": {}, + "source": [ + "## Chaining\n", + "\n", + "We can easily combine this message history class with [LCEL Runnables](/docs/expression_language/how_to/message_history)\n", + "\n", + "To do this we will want to use OpenAI, so we need to install that. We will also need to set the OPENAI_API_KEY environment variable to your OpenAI key.\n", + "\n", + "```bash\n", + "pip install -U langchain-openai\n", + "\n", + "export OPENAI_API_KEY='sk-xxxxxxx'\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6558418b-0ece-4d01-9661-56d562d78f7a", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Optional\n", + "\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_openai import ChatOpenAI" ] }, { "cell_type": "code", "execution_count": 4, - "id": "7e71f1dc", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 35 - }, - "id": "YsFE3hBjC6gl", - "outputId": "56ab5ca9-e343-41b5-e69d-47541718a9b4" - }, + "id": "82149122-61d3-490d-9bdb-bb98606e8ba1", + "metadata": {}, + "outputs": [], + "source": [ + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", \"You are a helpful assistant.\"),\n", + " MessagesPlaceholder(variable_name=\"history\"),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "\n", + "chain = prompt | ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2df90853-b67c-490f-b7f8-b69d69270b9c", + "metadata": {}, + "outputs": [], + "source": [ + "chain_with_history = RunnableWithMessageHistory(\n", + " chain,\n", + " lambda session_id: SQLChatMessageHistory(\n", + " session_id=session_id, connection_string=\"sqlite:///sqlite.db\"\n", + " ),\n", + " input_messages_key=\"question\",\n", + " history_messages_key=\"history\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0ce596b8-3b78-48fd-9f92-46dccbbfd58b", + "metadata": {}, + "outputs": [], + "source": [ + "# This is where we configure the session id\n", + "config = {\"configurable\": {\"session_id\": \"<SQL_SESSION_ID>\"}}" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "38e1423b-ba86-4496-9151-25932fab1a8b", + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Deven is working on a hackathon project with Sam.'" + "AIMessage(content='Hello Bob! How can I assist you today?')" ] }, - "execution_count": 4, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "conversation.memory.entity_store.get(\"Deven\")" + "chain_with_history.invoke({\"question\": \"Hi! I'm bob\"}, config=config)" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "316f2e8d", + "execution_count": 10, + "id": "2ee4ee62-a216-4fb1-bf33-57476a84cf16", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Sam is working on a hackathon project with Deven.'" + "AIMessage(content='Your name is Bob! Is there anything specific you would like assistance with, Bob?')" ] }, - "execution_count": 5, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "conversation.memory.entity_store.get(\"Sam\")" + "chain_with_history.invoke({\"question\": \"Whats my name\"}, config=config)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b85f8427", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 859748419577b9a3a5935524732fb8c254d08ad8 Mon Sep 17 00:00:00 2001 From: ChengZi <chen.zhang@zilliz.com> Date: Thu, 18 Jan 2024 01:41:23 +0800 Subject: [PATCH 057/215] langchain[patch]: support more comparators in Milvus self-querying retriever (#16076) - **Description:** Support IN and LIKE comparators in Milvus self-querying retriever, based on [Boolean Expression Rules](https://milvus.io/docs/boolean.md) - **Issue:** No - **Dependencies:** No - **Twitter handle:** No Signed-off-by: ChengZi <chen.zhang@zilliz.com> --- .../langchain/retrievers/self_query/milvus.py | 20 ++++++++++++---- .../retrievers/self_query/test_milvus.py | 23 +++++++++++++++---- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libs/langchain/langchain/retrievers/self_query/milvus.py b/libs/langchain/langchain/retrievers/self_query/milvus.py index f855bf9909398..dbc61f6f71203 100644 --- a/libs/langchain/langchain/retrievers/self_query/milvus.py +++ b/libs/langchain/langchain/retrievers/self_query/milvus.py @@ -16,28 +16,36 @@ Comparator.GTE: ">=", Comparator.LT: "<", Comparator.LTE: "<=", + Comparator.IN: "in", + Comparator.LIKE: "like", } UNARY_OPERATORS = [Operator.NOT] -def process_value(value: Union[int, float, str]) -> str: +def process_value(value: Union[int, float, str], comparator: Comparator) -> str: """Convert a value to a string and add double quotes if it is a string. It required for comparators involving strings. Args: value: The value to convert. + comparator: The comparator. Returns: The converted value as a string. """ # if isinstance(value, str): - # If the value is already a string, add double quotes - return f'"{value}"' + if comparator is Comparator.LIKE: + # If the comparator is LIKE, add a percent sign after it for prefix matching + # and add double quotes + return f'"{value}%"' + else: + # If the value is already a string, add double quotes + return f'"{value}"' else: - # If the valueis not a string, convert it to a string without double quotes + # If the value is not a string, convert it to a string without double quotes return str(value) @@ -54,6 +62,8 @@ class MilvusTranslator(Visitor): Comparator.GTE, Comparator.LT, Comparator.LTE, + Comparator.IN, + Comparator.LIKE, ] def _format_func(self, func: Union[Operator, Comparator]) -> str: @@ -78,7 +88,7 @@ def visit_operation(self, operation: Operation) -> str: def visit_comparison(self, comparison: Comparison) -> str: comparator = self._format_func(comparison.comparator) - processed_value = process_value(comparison.value) + processed_value = process_value(comparison.value, comparison.comparator) attribute = comparison.attribute return "( " + attribute + " " + comparator + " " + processed_value + " )" diff --git a/libs/langchain/tests/unit_tests/retrievers/self_query/test_milvus.py b/libs/langchain/tests/unit_tests/retrievers/self_query/test_milvus.py index d444974404413..4a96c18e274a6 100644 --- a/libs/langchain/tests/unit_tests/retrievers/self_query/test_milvus.py +++ b/libs/langchain/tests/unit_tests/retrievers/self_query/test_milvus.py @@ -1,4 +1,6 @@ -from typing import Dict, Tuple +from typing import Any, Dict, Tuple + +import pytest from langchain.chains.query_constructor.ir import ( Comparator, @@ -12,11 +14,22 @@ DEFAULT_TRANSLATOR = MilvusTranslator() -def test_visit_comparison() -> None: - comp = Comparison(comparator=Comparator.LT, attribute="foo", value=4) - expected = "( foo < 4 )" +@pytest.mark.parametrize( + "triplet", + [ + (Comparator.EQ, 2, "( foo == 2 )"), + (Comparator.GT, 2, "( foo > 2 )"), + (Comparator.GTE, 2, "( foo >= 2 )"), + (Comparator.LT, 2, "( foo < 2 )"), + (Comparator.LTE, 2, "( foo <= 2 )"), + (Comparator.IN, ["bar", "abc"], "( foo in ['bar', 'abc'] )"), + (Comparator.LIKE, "bar", '( foo like "bar%" )'), + ], +) +def test_visit_comparison(triplet: Tuple[Comparator, Any, str]) -> None: + comparator, value, expected = triplet + comp = Comparison(comparator=comparator, attribute="foo", value=value) actual = DEFAULT_TRANSLATOR.visit_comparison(comp) - assert expected == actual From bc0cb1148ac900eabea129ceef4a214c87af6890 Mon Sep 17 00:00:00 2001 From: Joshua Carroll <joshua.carroll@snowflake.com> Date: Wed, 17 Jan 2024 09:42:10 -0800 Subject: [PATCH 058/215] docs: Fix StreamlitChatMessageHistory docs to latest API (#16072) - **Description:** Update [this page](https://python.langchain.com/docs/integrations/memory/streamlit_chat_message_history) to use the latest API - **Issue:** https://github.com/langchain-ai/langchain/issues/13995 - **Dependencies:** None - **Twitter handle:** @OhSynap --- .../streamlit_chat_message_history.ipynb | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/docs/docs/integrations/memory/streamlit_chat_message_history.ipynb b/docs/docs/integrations/memory/streamlit_chat_message_history.ipynb index 21de8c78ac48a..987d6aecccea4 100644 --- a/docs/docs/integrations/memory/streamlit_chat_message_history.ipynb +++ b/docs/docs/integrations/memory/streamlit_chat_message_history.ipynb @@ -10,7 +10,6 @@ ">[Streamlit](https://docs.streamlit.io/) is an open-source Python library that makes it easy to create and share beautiful, \n", "custom web apps for machine learning and data science.\n", "\n", - "\n", "This notebook goes over how to store and use chat message history in a `Streamlit` app. `StreamlitChatMessageHistory` will store messages in\n", "[Streamlit session state](https://docs.streamlit.io/library/api-reference/session-state)\n", "at the specified `key=`. The default key is `\"langchain_messages\"`.\n", @@ -20,6 +19,12 @@ "- For more on Streamlit check out their\n", "[getting started documentation](https://docs.streamlit.io/library/get-started).\n", "\n", + "The integration lives in the `langchain-community` package, so we need to install that. We also need to install `streamlit`.\n", + "\n", + "```\n", + "pip install -U langchain-community streamlit\n", + "```\n", + "\n", "You can see the [full app example running here](https://langchain-st-memory.streamlit.app/), and more examples in\n", "[github.com/langchain-ai/streamlit-agent](https://github.com/langchain-ai/streamlit-agent)." ] @@ -31,7 +36,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.memory import StreamlitChatMessageHistory\n", + "from langchain_community.chat_message_histories import StreamlitChatMessageHistory\n", "\n", "history = StreamlitChatMessageHistory(key=\"chat_messages\")\n", "\n", @@ -54,7 +59,9 @@ "id": "b60dc735", "metadata": {}, "source": [ - "You can integrate `StreamlitChatMessageHistory` into `ConversationBufferMemory` and chains or agents as usual. The history will be persisted across re-runs of the Streamlit app within a given user session. A given `StreamlitChatMessageHistory` will NOT be persisted or shared across user sessions." + "We can easily combine this message history class with [LCEL Runnables](https://python.langchain.com/docs/expression_language/how_to/message_history).\n", + "\n", + "The history will be persisted across re-runs of the Streamlit app within a given user session. A given `StreamlitChatMessageHistory` will NOT be persisted or shared across user sessions." ] }, { @@ -64,13 +71,11 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.memory import ConversationBufferMemory\n", "from langchain_community.chat_message_histories import StreamlitChatMessageHistory\n", "\n", "# Optionally, specify your own session_state key for storing messages\n", "msgs = StreamlitChatMessageHistory(key=\"special_app_key\")\n", "\n", - "memory = ConversationBufferMemory(memory_key=\"history\", chat_memory=msgs)\n", "if len(msgs.messages) == 0:\n", " msgs.add_ai_message(\"How can I help you?\")" ] @@ -82,19 +87,34 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.chains import LLMChain\n", - "from langchain.prompts import PromptTemplate\n", - "from langchain_openai import OpenAI\n", - "\n", - "template = \"\"\"You are an AI chatbot having a conversation with a human.\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_openai import ChatOpenAI\n", "\n", - "{history}\n", - "Human: {human_input}\n", - "AI: \"\"\"\n", - "prompt = PromptTemplate(input_variables=[\"history\", \"human_input\"], template=template)\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", \"You are an AI chatbot having a conversation with a human.\"),\n", + " MessagesPlaceholder(variable_name=\"history\"),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", "\n", - "# Add the memory to an LLMChain as usual\n", - "llm_chain = LLMChain(llm=OpenAI(), prompt=prompt, memory=memory)" + "chain = prompt | ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dac3d94f", + "metadata": {}, + "outputs": [], + "source": [ + "chain_with_history = RunnableWithMessageHistory(\n", + " chain,\n", + " lambda session_id: msgs, # Always return the instance created earlier\n", + " input_messages_key=\"question\",\n", + " history_messages_key=\"history\",\n", + ")" ] }, { @@ -121,8 +141,9 @@ " st.chat_message(\"human\").write(prompt)\n", "\n", " # As usual, new messages are added to StreamlitChatMessageHistory when the Chain is called.\n", - " response = llm_chain.run(prompt)\n", - " st.chat_message(\"ai\").write(response)" + " config = {\"configurable\": {\"session_id\": \"any\"}}\n", + " response = chain_with_history.invoke({\"question\": prompt}, config)\n", + " st.chat_message(\"ai\").write(response.content)" ] }, { From d0e101e4e01c662dae153bd0ab0b5ef355e7a986 Mon Sep 17 00:00:00 2001 From: Fei Wang <fei.comm@icloud.com> Date: Thu, 18 Jan 2024 01:42:41 +0800 Subject: [PATCH 059/215] community[patch]: fix ollama astream (#16070) Update ollama.py --- libs/community/langchain_community/llms/ollama.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/llms/ollama.py b/libs/community/langchain_community/llms/ollama.py index 29a724b07e911..db8d66170483f 100644 --- a/libs/community/langchain_community/llms/ollama.py +++ b/libs/community/langchain_community/llms/ollama.py @@ -468,7 +468,7 @@ async def _astream( run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, **kwargs: Any, ) -> AsyncIterator[GenerationChunk]: - async for stream_resp in self._acreate_stream(prompt, stop, **kwargs): + async for stream_resp in self._acreate_generate_stream(prompt, stop, **kwargs): if stream_resp: chunk = _stream_response_to_generation_chunk(stream_resp) yield chunk From d350be959d22f05785bd1676652c156343f1b950 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Wed, 17 Jan 2024 09:58:42 -0800 Subject: [PATCH 060/215] langchain[patch]: updated `chains` imports (#16064) Updated imports into `langchain` to `core` where it is possible --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- libs/langchain/langchain/chains/api/base.py | 8 ++++---- .../langchain/chains/api/openapi/chain.py | 2 +- libs/langchain/langchain/chains/base.py | 16 ++++++++-------- .../langchain/chains/combine_documents/base.py | 8 ++++---- .../chains/combine_documents/map_reduce.py | 2 +- .../chains/combine_documents/map_rerank.py | 2 +- .../langchain/chains/combine_documents/reduce.py | 2 +- .../langchain/chains/combine_documents/refine.py | 2 +- .../langchain/chains/combine_documents/stuff.py | 2 +- .../langchain/chains/constitutional_ai/base.py | 2 +- .../chains/conversational_retrieval/base.py | 10 +++++----- .../chains/elasticsearch_database/base.py | 4 ++-- .../langchain/chains/ernie_functions/base.py | 13 ++++++++----- libs/langchain/langchain/chains/flare/base.py | 6 +++--- .../langchain/chains/graph_qa/arangodb.py | 4 ++-- .../langchain/chains/graph_qa/cypher.py | 2 +- .../langchain/chains/graph_qa/falkordb.py | 4 ++-- .../langchain/chains/graph_qa/hugegraph.py | 2 +- libs/langchain/langchain/chains/graph_qa/kuzu.py | 2 +- .../langchain/chains/graph_qa/nebulagraph.py | 2 +- .../langchain/chains/graph_qa/neptune_cypher.py | 4 ++-- .../langchain/chains/graph_qa/sparql.py | 2 +- libs/langchain/langchain/chains/hyde/base.py | 2 +- libs/langchain/langchain/chains/llm.py | 14 +++++++------- .../langchain/chains/llm_checker/base.py | 2 +- libs/langchain/langchain/chains/llm_math/base.py | 8 ++++---- libs/langchain/langchain/chains/llm_requests.py | 2 +- .../chains/llm_summarization_checker/base.py | 2 +- libs/langchain/langchain/chains/mapreduce.py | 2 +- libs/langchain/langchain/chains/moderation.py | 4 ++-- libs/langchain/langchain/chains/natbot/base.py | 2 +- .../langchain/chains/openai_functions/base.py | 2 +- .../langchain/chains/openai_functions/openapi.py | 2 +- .../langchain/chains/openai_tools/extraction.py | 2 +- .../langchain/chains/qa_generation/base.py | 2 +- .../langchain/chains/qa_with_sources/base.py | 8 ++++---- .../chains/qa_with_sources/retrieval.py | 8 ++++---- .../chains/qa_with_sources/vector_db.py | 8 ++++---- .../langchain/chains/query_constructor/base.py | 2 +- .../chains/question_answering/__init__.py | 3 +-- .../langchain/chains/retrieval_qa/base.py | 10 +++++----- libs/langchain/langchain/chains/router/base.py | 6 +++--- .../langchain/chains/router/embedding_router.py | 2 +- .../langchain/chains/router/llm_router.py | 8 ++++---- libs/langchain/langchain/chains/sequential.py | 8 ++++---- .../langchain/chains/sql_database/prompt.py | 2 +- .../langchain/chains/summarize/__init__.py | 2 +- libs/langchain/langchain/chains/transform.py | 6 +++--- 48 files changed, 111 insertions(+), 109 deletions(-) diff --git a/libs/langchain/langchain/chains/api/base.py b/libs/langchain/langchain/chains/api/base.py index 05b18330efc67..212c04845c7a6 100644 --- a/libs/langchain/langchain/chains/api/base.py +++ b/libs/langchain/langchain/chains/api/base.py @@ -5,14 +5,14 @@ from urllib.parse import urlparse from langchain_community.utilities.requests import TextRequestsWrapper +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field, root_validator -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains.api.prompt import API_RESPONSE_PROMPT, API_URL_PROMPT from langchain.chains.base import Chain from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/chains/api/openapi/chain.py b/libs/langchain/langchain/chains/api/openapi/chain.py index ead27fbf37da7..7fad47fedd8b3 100644 --- a/libs/langchain/langchain/chains/api/openapi/chain.py +++ b/libs/langchain/langchain/chains/api/openapi/chain.py @@ -6,11 +6,11 @@ from langchain_community.tools.openapi.utils.api_models import APIOperation from langchain_community.utilities.requests import Requests +from langchain_core.callbacks import CallbackManagerForChainRun, Callbacks from langchain_core.language_models import BaseLanguageModel from langchain_core.pydantic_v1 import BaseModel, Field from requests import Response -from langchain.callbacks.manager import CallbackManagerForChainRun, Callbacks from langchain.chains.api.openapi.requests_chain import APIRequesterChain from langchain.chains.api.openapi.response_chain import APIResponderChain from langchain.chains.base import Chain diff --git a/libs/langchain/langchain/chains/base.py b/libs/langchain/langchain/chains/base.py index 1d02272c01ea2..2afcac43b8519 100644 --- a/libs/langchain/langchain/chains/base.py +++ b/libs/langchain/langchain/chains/base.py @@ -9,6 +9,14 @@ import yaml from langchain_core._api import deprecated +from langchain_core.callbacks import ( + AsyncCallbackManager, + AsyncCallbackManagerForChainRun, + BaseCallbackManager, + CallbackManager, + CallbackManagerForChainRun, + Callbacks, +) from langchain_core.load.dump import dumpd from langchain_core.memory import BaseMemory from langchain_core.outputs import RunInfo @@ -26,14 +34,6 @@ run_in_executor, ) -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import ( - AsyncCallbackManager, - AsyncCallbackManagerForChainRun, - CallbackManager, - CallbackManagerForChainRun, - Callbacks, -) from langchain.schema import RUN_KEY logger = logging.getLogger(__name__) diff --git a/libs/langchain/langchain/chains/combine_documents/base.py b/libs/langchain/langchain/chains/combine_documents/base.py index 019d9a6e2b3a8..7285fced33f3d 100644 --- a/libs/langchain/langchain/chains/combine_documents/base.py +++ b/libs/langchain/langchain/chains/combine_documents/base.py @@ -3,15 +3,15 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Tuple, Type +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.documents import Document from langchain_core.prompts import BasePromptTemplate, PromptTemplate from langchain_core.pydantic_v1 import BaseModel, Field, create_model from langchain_core.runnables.config import RunnableConfig -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains.base import Chain from langchain.text_splitter import RecursiveCharacterTextSplitter, TextSplitter diff --git a/libs/langchain/langchain/chains/combine_documents/map_reduce.py b/libs/langchain/langchain/chains/combine_documents/map_reduce.py index ef3a0fb7bf383..18be6d4cf279b 100644 --- a/libs/langchain/langchain/chains/combine_documents/map_reduce.py +++ b/libs/langchain/langchain/chains/combine_documents/map_reduce.py @@ -4,11 +4,11 @@ from typing import Any, Dict, List, Optional, Tuple, Type +from langchain_core.callbacks import Callbacks from langchain_core.documents import Document from langchain_core.pydantic_v1 import BaseModel, Extra, create_model, root_validator from langchain_core.runnables.config import RunnableConfig -from langchain.callbacks.manager import Callbacks from langchain.chains.combine_documents.base import BaseCombineDocumentsChain from langchain.chains.combine_documents.reduce import ReduceDocumentsChain from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/chains/combine_documents/map_rerank.py b/libs/langchain/langchain/chains/combine_documents/map_rerank.py index d3ac0410942a8..0466aac56b941 100644 --- a/libs/langchain/langchain/chains/combine_documents/map_rerank.py +++ b/libs/langchain/langchain/chains/combine_documents/map_rerank.py @@ -4,11 +4,11 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union, cast +from langchain_core.callbacks import Callbacks from langchain_core.documents import Document from langchain_core.pydantic_v1 import BaseModel, Extra, create_model, root_validator from langchain_core.runnables.config import RunnableConfig -from langchain.callbacks.manager import Callbacks from langchain.chains.combine_documents.base import BaseCombineDocumentsChain from langchain.chains.llm import LLMChain from langchain.output_parsers.regex import RegexParser diff --git a/libs/langchain/langchain/chains/combine_documents/reduce.py b/libs/langchain/langchain/chains/combine_documents/reduce.py index 74e53c1fbd829..00e3efd02e076 100644 --- a/libs/langchain/langchain/chains/combine_documents/reduce.py +++ b/libs/langchain/langchain/chains/combine_documents/reduce.py @@ -4,10 +4,10 @@ from typing import Any, Callable, List, Optional, Protocol, Tuple +from langchain_core.callbacks import Callbacks from langchain_core.documents import Document from langchain_core.pydantic_v1 import Extra -from langchain.callbacks.manager import Callbacks from langchain.chains.combine_documents.base import BaseCombineDocumentsChain diff --git a/libs/langchain/langchain/chains/combine_documents/refine.py b/libs/langchain/langchain/chains/combine_documents/refine.py index 7a6c495328f07..dc34e1594b0a9 100644 --- a/libs/langchain/langchain/chains/combine_documents/refine.py +++ b/libs/langchain/langchain/chains/combine_documents/refine.py @@ -4,12 +4,12 @@ from typing import Any, Dict, List, Tuple +from langchain_core.callbacks import Callbacks from langchain_core.documents import Document from langchain_core.prompts import BasePromptTemplate, format_document from langchain_core.prompts.prompt import PromptTemplate from langchain_core.pydantic_v1 import Extra, Field, root_validator -from langchain.callbacks.manager import Callbacks from langchain.chains.combine_documents.base import ( BaseCombineDocumentsChain, ) diff --git a/libs/langchain/langchain/chains/combine_documents/stuff.py b/libs/langchain/langchain/chains/combine_documents/stuff.py index 73183687e984f..d965eb4219754 100644 --- a/libs/langchain/langchain/chains/combine_documents/stuff.py +++ b/libs/langchain/langchain/chains/combine_documents/stuff.py @@ -1,6 +1,7 @@ """Chain that combines documents by stuffing into context.""" from typing import Any, Dict, List, Optional, Tuple +from langchain_core.callbacks import Callbacks from langchain_core.documents import Document from langchain_core.language_models import LanguageModelLike from langchain_core.output_parsers import BaseOutputParser, StrOutputParser @@ -8,7 +9,6 @@ from langchain_core.pydantic_v1 import Extra, Field, root_validator from langchain_core.runnables import Runnable, RunnablePassthrough -from langchain.callbacks.manager import Callbacks from langchain.chains.combine_documents.base import ( DEFAULT_DOCUMENT_PROMPT, DEFAULT_DOCUMENT_SEPARATOR, diff --git a/libs/langchain/langchain/chains/constitutional_ai/base.py b/libs/langchain/langchain/chains/constitutional_ai/base.py index 33b3886f40d97..90017cc7b53a1 100644 --- a/libs/langchain/langchain/chains/constitutional_ai/base.py +++ b/libs/langchain/langchain/chains/constitutional_ai/base.py @@ -1,10 +1,10 @@ """Chain for applying constitutional principles to the outputs of another chain.""" from typing import Any, Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.constitutional_ai.models import ConstitutionalPrinciple from langchain.chains.constitutional_ai.principles import PRINCIPLES diff --git a/libs/langchain/langchain/chains/conversational_retrieval/base.py b/libs/langchain/langchain/chains/conversational_retrieval/base.py index 456186f977830..c92f18f7e8570 100644 --- a/libs/langchain/langchain/chains/conversational_retrieval/base.py +++ b/libs/langchain/langchain/chains/conversational_retrieval/base.py @@ -7,6 +7,11 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, + Callbacks, +) from langchain_core.documents import Document from langchain_core.language_models import BaseLanguageModel from langchain_core.messages import BaseMessage @@ -16,11 +21,6 @@ from langchain_core.runnables import RunnableConfig from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, - Callbacks, -) from langchain.chains.base import Chain from langchain.chains.combine_documents.base import BaseCombineDocumentsChain from langchain.chains.combine_documents.stuff import StuffDocumentsChain diff --git a/libs/langchain/langchain/chains/elasticsearch_database/base.py b/libs/langchain/langchain/chains/elasticsearch_database/base.py index 46c336808f1d6..f4e839125dd29 100644 --- a/libs/langchain/langchain/chains/elasticsearch_database/base.py +++ b/libs/langchain/langchain/chains/elasticsearch_database/base.py @@ -3,16 +3,16 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.output_parsers import BaseLLMOutputParser +from langchain_core.output_parsers.json import SimpleJsonOutputParser from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Extra, root_validator -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.elasticsearch_database.prompts import ANSWER_PROMPT, DSL_PROMPT from langchain.chains.llm import LLMChain -from langchain.output_parsers.json import SimpleJsonOutputParser if TYPE_CHECKING: from elasticsearch import Elasticsearch diff --git a/libs/langchain/langchain/chains/ernie_functions/base.py b/libs/langchain/langchain/chains/ernie_functions/base.py index afed9d4257db2..9a12b70c97a3c 100644 --- a/libs/langchain/langchain/chains/ernie_functions/base.py +++ b/libs/langchain/langchain/chains/ernie_functions/base.py @@ -13,19 +13,22 @@ cast, ) -from langchain_core.output_parsers import BaseGenerationOutputParser, BaseOutputParser +from langchain_core.language_models import BaseLanguageModel +from langchain_core.output_parsers import ( + BaseGenerationOutputParser, + BaseLLMOutputParser, + BaseOutputParser, +) +from langchain_core.prompts import BasePromptTemplate +from langchain_core.pydantic_v1 import BaseModel from langchain_core.runnables import Runnable -from langchain.base_language import BaseLanguageModel from langchain.chains import LLMChain from langchain.output_parsers.ernie_functions import ( JsonOutputFunctionsParser, PydanticAttrOutputFunctionsParser, PydanticOutputFunctionsParser, ) -from langchain.prompts import BasePromptTemplate -from langchain.pydantic_v1 import BaseModel -from langchain.schema import BaseLLMOutputParser from langchain.utils.ernie_functions import convert_pydantic_to_ernie_function PYTHON_TO_JSON_TYPES = { diff --git a/libs/langchain/langchain/chains/flare/base.py b/libs/langchain/langchain/chains/flare/base.py index 06b58ca6d06be..c2ef8f2da60ca 100644 --- a/libs/langchain/langchain/chains/flare/base.py +++ b/libs/langchain/langchain/chains/flare/base.py @@ -6,15 +6,15 @@ import numpy as np from langchain_community.llms.openai import OpenAI +from langchain_core.callbacks import ( + CallbackManagerForChainRun, +) from langchain_core.language_models import BaseLanguageModel from langchain_core.outputs import Generation from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field from langchain_core.retrievers import BaseRetriever -from langchain.callbacks.manager import ( - CallbackManagerForChainRun, -) from langchain.chains.base import Chain from langchain.chains.flare.prompts import ( PROMPT, diff --git a/libs/langchain/langchain/chains/graph_qa/arangodb.py b/libs/langchain/langchain/chains/graph_qa/arangodb.py index ac797f71953ae..4c723e2046fab 100644 --- a/libs/langchain/langchain/chains/graph_qa/arangodb.py +++ b/libs/langchain/langchain/chains/graph_qa/arangodb.py @@ -5,11 +5,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs.arangodb_graph import ArangoGraph +from langchain_core.callbacks import CallbackManagerForChainRun +from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.base_language import BaseLanguageModel -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import ( AQL_FIX_PROMPT, diff --git a/libs/langchain/langchain/chains/graph_qa/cypher.py b/libs/langchain/langchain/chains/graph_qa/cypher.py index c15837cce66a1..3376e56730803 100644 --- a/libs/langchain/langchain/chains/graph_qa/cypher.py +++ b/libs/langchain/langchain/chains/graph_qa/cypher.py @@ -5,11 +5,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs.graph_store import GraphStore +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema from langchain.chains.graph_qa.prompts import CYPHER_GENERATION_PROMPT, CYPHER_QA_PROMPT diff --git a/libs/langchain/langchain/chains/graph_qa/falkordb.py b/libs/langchain/langchain/chains/graph_qa/falkordb.py index 6c9f7110df708..b7ead3d8714e0 100644 --- a/libs/langchain/langchain/chains/graph_qa/falkordb.py +++ b/libs/langchain/langchain/chains/graph_qa/falkordb.py @@ -5,11 +5,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs import FalkorDBGraph +from langchain_core.callbacks import CallbackManagerForChainRun +from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.base_language import BaseLanguageModel -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import CYPHER_GENERATION_PROMPT, CYPHER_QA_PROMPT from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/chains/graph_qa/hugegraph.py b/libs/langchain/langchain/chains/graph_qa/hugegraph.py index 0ca54111cb0d0..9e1f02493798b 100644 --- a/libs/langchain/langchain/chains/graph_qa/hugegraph.py +++ b/libs/langchain/langchain/chains/graph_qa/hugegraph.py @@ -4,11 +4,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs.hugegraph import HugeGraph +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import ( CYPHER_QA_PROMPT, diff --git a/libs/langchain/langchain/chains/graph_qa/kuzu.py b/libs/langchain/langchain/chains/graph_qa/kuzu.py index 3735e822833f8..7df4cdc846584 100644 --- a/libs/langchain/langchain/chains/graph_qa/kuzu.py +++ b/libs/langchain/langchain/chains/graph_qa/kuzu.py @@ -4,11 +4,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs.kuzu_graph import KuzuGraph +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import CYPHER_QA_PROMPT, KUZU_GENERATION_PROMPT from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/chains/graph_qa/nebulagraph.py b/libs/langchain/langchain/chains/graph_qa/nebulagraph.py index d722c3a8510d6..e53eaefb35e13 100644 --- a/libs/langchain/langchain/chains/graph_qa/nebulagraph.py +++ b/libs/langchain/langchain/chains/graph_qa/nebulagraph.py @@ -4,11 +4,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs.nebula_graph import NebulaGraph +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import CYPHER_QA_PROMPT, NGQL_GENERATION_PROMPT from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/chains/graph_qa/neptune_cypher.py b/libs/langchain/langchain/chains/graph_qa/neptune_cypher.py index 74985029d43be..8fec19f5e1d8d 100644 --- a/libs/langchain/langchain/chains/graph_qa/neptune_cypher.py +++ b/libs/langchain/langchain/chains/graph_qa/neptune_cypher.py @@ -4,11 +4,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs import NeptuneGraph +from langchain_core.callbacks import CallbackManagerForChainRun +from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts.base import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.base_language import BaseLanguageModel -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import ( CYPHER_QA_PROMPT, diff --git a/libs/langchain/langchain/chains/graph_qa/sparql.py b/libs/langchain/langchain/chains/graph_qa/sparql.py index 1d8150b4bab3d..f9d4897090070 100644 --- a/libs/langchain/langchain/chains/graph_qa/sparql.py +++ b/libs/langchain/langchain/chains/graph_qa/sparql.py @@ -6,11 +6,11 @@ from typing import Any, Dict, List, Optional from langchain_community.graphs.rdf_graph import RdfGraph +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts.base import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.graph_qa.prompts import ( SPARQL_GENERATION_SELECT_PROMPT, diff --git a/libs/langchain/langchain/chains/hyde/base.py b/libs/langchain/langchain/chains/hyde/base.py index a9573f6b06804..ca658aea93c01 100644 --- a/libs/langchain/langchain/chains/hyde/base.py +++ b/libs/langchain/langchain/chains/hyde/base.py @@ -7,12 +7,12 @@ from typing import Any, Dict, List, Optional import numpy as np +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.embeddings import Embeddings from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Extra -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.hyde.prompts import PROMPT_MAP from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/chains/llm.py b/libs/langchain/langchain/chains/llm.py index 2d9908643a185..f142902840593 100644 --- a/libs/langchain/langchain/chains/llm.py +++ b/libs/langchain/langchain/chains/llm.py @@ -4,6 +4,13 @@ import warnings from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast +from langchain_core.callbacks import ( + AsyncCallbackManager, + AsyncCallbackManagerForChainRun, + CallbackManager, + CallbackManagerForChainRun, + Callbacks, +) from langchain_core.language_models import ( BaseLanguageModel, LanguageModelInput, @@ -24,13 +31,6 @@ from langchain_core.runnables.configurable import DynamicRunnable from langchain_core.utils.input import get_colored_text -from langchain.callbacks.manager import ( - AsyncCallbackManager, - AsyncCallbackManagerForChainRun, - CallbackManager, - CallbackManagerForChainRun, - Callbacks, -) from langchain.chains.base import Chain diff --git a/libs/langchain/langchain/chains/llm_checker/base.py b/libs/langchain/langchain/chains/llm_checker/base.py index 4e718c73457b1..1d9166364e696 100644 --- a/libs/langchain/langchain/chains/llm_checker/base.py +++ b/libs/langchain/langchain/chains/llm_checker/base.py @@ -4,11 +4,11 @@ import warnings from typing import Any, Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import PromptTemplate from langchain_core.pydantic_v1 import Extra, root_validator -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.llm_checker.prompt import ( diff --git a/libs/langchain/langchain/chains/llm_math/base.py b/libs/langchain/langchain/chains/llm_math/base.py index 7e520ef0c4f26..cada7dc3fa38c 100644 --- a/libs/langchain/langchain/chains/llm_math/base.py +++ b/libs/langchain/langchain/chains/llm_math/base.py @@ -6,14 +6,14 @@ import warnings from typing import Any, Dict, List, Optional +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Extra, root_validator -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.llm_math.prompt import PROMPT diff --git a/libs/langchain/langchain/chains/llm_requests.py b/libs/langchain/langchain/chains/llm_requests.py index 8daf49f4eee37..ed79f92f97105 100644 --- a/libs/langchain/langchain/chains/llm_requests.py +++ b/libs/langchain/langchain/chains/llm_requests.py @@ -4,9 +4,9 @@ from typing import Any, Dict, List, Optional from langchain_community.utilities.requests import TextRequestsWrapper +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.pydantic_v1 import Extra, Field, root_validator -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains import LLMChain from langchain.chains.base import Chain diff --git a/libs/langchain/langchain/chains/llm_summarization_checker/base.py b/libs/langchain/langchain/chains/llm_summarization_checker/base.py index a3659a53dbe57..b5d1c1d504a5e 100644 --- a/libs/langchain/langchain/chains/llm_summarization_checker/base.py +++ b/libs/langchain/langchain/chains/llm_summarization_checker/base.py @@ -6,11 +6,11 @@ from pathlib import Path from typing import Any, Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts.prompt import PromptTemplate from langchain_core.pydantic_v1 import Extra, root_validator -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.sequential import SequentialChain diff --git a/libs/langchain/langchain/chains/mapreduce.py b/libs/langchain/langchain/chains/mapreduce.py index f8e29b58aa7dc..0bcf9907ba653 100644 --- a/libs/langchain/langchain/chains/mapreduce.py +++ b/libs/langchain/langchain/chains/mapreduce.py @@ -7,12 +7,12 @@ from typing import Any, Dict, List, Mapping, Optional +from langchain_core.callbacks import CallbackManagerForChainRun, Callbacks from langchain_core.documents import Document from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Extra -from langchain.callbacks.manager import CallbackManagerForChainRun, Callbacks from langchain.chains import ReduceDocumentsChain from langchain.chains.base import Chain from langchain.chains.combine_documents.base import BaseCombineDocumentsChain diff --git a/libs/langchain/langchain/chains/moderation.py b/libs/langchain/langchain/chains/moderation.py index c2935f3feb317..00d6cbd3f3528 100644 --- a/libs/langchain/langchain/chains/moderation.py +++ b/libs/langchain/langchain/chains/moderation.py @@ -1,11 +1,11 @@ """Pass input through a moderation endpoint.""" from typing import Any, Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.pydantic_v1 import root_validator +from langchain_core.utils import get_from_dict_or_env -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain -from langchain.utils import get_from_dict_or_env class OpenAIModerationChain(Chain): diff --git a/libs/langchain/langchain/chains/natbot/base.py b/libs/langchain/langchain/chains/natbot/base.py index 9e9d521e541c2..e74c3477b1419 100644 --- a/libs/langchain/langchain/chains/natbot/base.py +++ b/libs/langchain/langchain/chains/natbot/base.py @@ -5,10 +5,10 @@ from typing import Any, Dict, List, Optional from langchain_community.llms.openai import OpenAI +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.pydantic_v1 import Extra, root_validator -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.natbot.prompt import PROMPT diff --git a/libs/langchain/langchain/chains/openai_functions/base.py b/libs/langchain/langchain/chains/openai_functions/base.py index e94f1fded2433..7f8fdd4431b48 100644 --- a/libs/langchain/langchain/chains/openai_functions/base.py +++ b/libs/langchain/langchain/chains/openai_functions/base.py @@ -10,6 +10,7 @@ ) from langchain_core._api import deprecated +from langchain_core.language_models import BaseLanguageModel from langchain_core.output_parsers import ( BaseGenerationOutputParser, BaseLLMOutputParser, @@ -23,7 +24,6 @@ convert_to_openai_function, ) -from langchain.base_language import BaseLanguageModel from langchain.chains import LLMChain from langchain.output_parsers.openai_functions import ( JsonOutputFunctionsParser, diff --git a/libs/langchain/langchain/chains/openai_functions/openapi.py b/libs/langchain/langchain/chains/openai_functions/openapi.py index 549872f25b2e8..4b1bc2e7faca7 100644 --- a/libs/langchain/langchain/chains/openai_functions/openapi.py +++ b/libs/langchain/langchain/chains/openai_functions/openapi.py @@ -8,12 +8,12 @@ import requests from langchain_community.chat_models import ChatOpenAI from langchain_community.utilities.openapi import OpenAPISpec +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate from langchain_core.utils.input import get_colored_text from requests import Response -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.sequential import SequentialChain diff --git a/libs/langchain/langchain/chains/openai_tools/extraction.py b/libs/langchain/langchain/chains/openai_tools/extraction.py index 6e7ebe65a07cc..eda3e4bd59f09 100644 --- a/libs/langchain/langchain/chains/openai_tools/extraction.py +++ b/libs/langchain/langchain/chains/openai_tools/extraction.py @@ -4,9 +4,9 @@ from langchain_core.prompts import ChatPromptTemplate from langchain_core.pydantic_v1 import BaseModel from langchain_core.runnables import Runnable +from langchain_core.utils.function_calling import convert_pydantic_to_openai_function from langchain.output_parsers import PydanticToolsParser -from langchain.utils.openai_functions import convert_pydantic_to_openai_function _EXTRACTION_TEMPLATE = """Extract and save the relevant entities mentioned \ in the following passage together with their properties. diff --git a/libs/langchain/langchain/chains/qa_generation/base.py b/libs/langchain/langchain/chains/qa_generation/base.py index cbb0eb36bac1c..d2ce08c80eca9 100644 --- a/libs/langchain/langchain/chains/qa_generation/base.py +++ b/libs/langchain/langchain/chains/qa_generation/base.py @@ -3,11 +3,11 @@ import json from typing import Any, Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.chains.qa_generation.prompt import PROMPT_SELECTOR diff --git a/libs/langchain/langchain/chains/qa_with_sources/base.py b/libs/langchain/langchain/chains/qa_with_sources/base.py index 2144824a0922f..02a1b3aa0a4ee 100644 --- a/libs/langchain/langchain/chains/qa_with_sources/base.py +++ b/libs/langchain/langchain/chains/qa_with_sources/base.py @@ -7,15 +7,15 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Tuple +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.documents import Document from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Extra, root_validator -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains import ReduceDocumentsChain from langchain.chains.base import Chain from langchain.chains.combine_documents.base import BaseCombineDocumentsChain diff --git a/libs/langchain/langchain/chains/qa_with_sources/retrieval.py b/libs/langchain/langchain/chains/qa_with_sources/retrieval.py index a2b5c56265268..9642f626ad61a 100644 --- a/libs/langchain/langchain/chains/qa_with_sources/retrieval.py +++ b/libs/langchain/langchain/chains/qa_with_sources/retrieval.py @@ -2,14 +2,14 @@ from typing import Any, Dict, List +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.documents import Document from langchain_core.pydantic_v1 import Field from langchain_core.retrievers import BaseRetriever -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains.combine_documents.stuff import StuffDocumentsChain from langchain.chains.qa_with_sources.base import BaseQAWithSourcesChain diff --git a/libs/langchain/langchain/chains/qa_with_sources/vector_db.py b/libs/langchain/langchain/chains/qa_with_sources/vector_db.py index 6feddc5bb778c..31e97bbf22e00 100644 --- a/libs/langchain/langchain/chains/qa_with_sources/vector_db.py +++ b/libs/langchain/langchain/chains/qa_with_sources/vector_db.py @@ -3,14 +3,14 @@ import warnings from typing import Any, Dict, List +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.documents import Document from langchain_core.pydantic_v1 import Field, root_validator from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains.combine_documents.stuff import StuffDocumentsChain from langchain.chains.qa_with_sources.base import BaseQAWithSourcesChain diff --git a/libs/langchain/langchain/chains/query_constructor/base.py b/libs/langchain/langchain/chains/query_constructor/base.py index ecbffe18163c8..d99c046abfadf 100644 --- a/libs/langchain/langchain/chains/query_constructor/base.py +++ b/libs/langchain/langchain/chains/query_constructor/base.py @@ -7,6 +7,7 @@ from langchain_core.exceptions import OutputParserException from langchain_core.language_models import BaseLanguageModel from langchain_core.output_parsers import BaseOutputParser +from langchain_core.output_parsers.json import parse_and_check_json_markdown from langchain_core.prompts import BasePromptTemplate from langchain_core.prompts.few_shot import FewShotPromptTemplate from langchain_core.runnables import Runnable @@ -34,7 +35,6 @@ USER_SPECIFIED_EXAMPLE_PROMPT, ) from langchain.chains.query_constructor.schema import AttributeInfo -from langchain.output_parsers.json import parse_and_check_json_markdown class StructuredQueryOutputParser(BaseOutputParser[StructuredQuery]): diff --git a/libs/langchain/langchain/chains/question_answering/__init__.py b/libs/langchain/langchain/chains/question_answering/__init__.py index 3fd5070f1a7bd..34d6c917626de 100644 --- a/libs/langchain/langchain/chains/question_answering/__init__.py +++ b/libs/langchain/langchain/chains/question_answering/__init__.py @@ -1,11 +1,10 @@ """Load question answering chains.""" from typing import Any, Mapping, Optional, Protocol +from langchain_core.callbacks import BaseCallbackManager, Callbacks from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import Callbacks from langchain.chains import ReduceDocumentsChain from langchain.chains.combine_documents.base import BaseCombineDocumentsChain from langchain.chains.combine_documents.map_reduce import MapReduceDocumentsChain diff --git a/libs/langchain/langchain/chains/retrieval_qa/base.py b/libs/langchain/langchain/chains/retrieval_qa/base.py index 94fd932078e19..392a0b2cc442f 100644 --- a/libs/langchain/langchain/chains/retrieval_qa/base.py +++ b/libs/langchain/langchain/chains/retrieval_qa/base.py @@ -6,6 +6,11 @@ from abc import abstractmethod from typing import Any, Dict, List, Optional +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, + Callbacks, +) from langchain_core.documents import Document from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import PromptTemplate @@ -13,11 +18,6 @@ from langchain_core.retrievers import BaseRetriever from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, - Callbacks, -) from langchain.chains.base import Chain from langchain.chains.combine_documents.base import BaseCombineDocumentsChain from langchain.chains.combine_documents.stuff import StuffDocumentsChain diff --git a/libs/langchain/langchain/chains/router/base.py b/libs/langchain/langchain/chains/router/base.py index c2acb27f91536..e6a9788d0f6dd 100644 --- a/libs/langchain/langchain/chains/router/base.py +++ b/libs/langchain/langchain/chains/router/base.py @@ -4,13 +4,13 @@ from abc import ABC from typing import Any, Dict, List, Mapping, NamedTuple, Optional -from langchain_core.pydantic_v1 import Extra - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManagerForChainRun, CallbackManagerForChainRun, Callbacks, ) +from langchain_core.pydantic_v1 import Extra + from langchain.chains.base import Chain diff --git a/libs/langchain/langchain/chains/router/embedding_router.py b/libs/langchain/langchain/chains/router/embedding_router.py index a7efd41a5df10..b8f9d975bd953 100644 --- a/libs/langchain/langchain/chains/router/embedding_router.py +++ b/libs/langchain/langchain/chains/router/embedding_router.py @@ -2,12 +2,12 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type +from langchain_core.callbacks import CallbackManagerForChainRun from langchain_core.documents import Document from langchain_core.embeddings import Embeddings from langchain_core.pydantic_v1 import Extra from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import CallbackManagerForChainRun from langchain.chains.router.base import RouterChain diff --git a/libs/langchain/langchain/chains/router/llm_router.py b/libs/langchain/langchain/chains/router/llm_router.py index 03662160c1bd2..9443321827a61 100644 --- a/libs/langchain/langchain/chains/router/llm_router.py +++ b/libs/langchain/langchain/chains/router/llm_router.py @@ -3,16 +3,16 @@ from typing import Any, Dict, List, Optional, Type, cast +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + CallbackManagerForChainRun, +) from langchain_core.exceptions import OutputParserException from langchain_core.language_models import BaseLanguageModel from langchain_core.output_parsers import BaseOutputParser from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import root_validator -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) from langchain.chains import LLMChain from langchain.chains.router.base import RouterChain from langchain.output_parsers.json import parse_and_check_json_markdown diff --git a/libs/langchain/langchain/chains/sequential.py b/libs/langchain/langchain/chains/sequential.py index d9462434fd44d..29cf5dc04d4e8 100644 --- a/libs/langchain/langchain/chains/sequential.py +++ b/libs/langchain/langchain/chains/sequential.py @@ -1,13 +1,13 @@ """Chain pipeline where the outputs of one step feed directly into next.""" from typing import Any, Dict, List, Optional -from langchain_core.pydantic_v1 import Extra, root_validator -from langchain_core.utils.input import get_color_mapping - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManagerForChainRun, CallbackManagerForChainRun, ) +from langchain_core.pydantic_v1 import Extra, root_validator +from langchain_core.utils.input import get_color_mapping + from langchain.chains.base import Chain diff --git a/libs/langchain/langchain/chains/sql_database/prompt.py b/libs/langchain/langchain/chains/sql_database/prompt.py index 34cd2307b6b38..d11740e4b4b29 100644 --- a/libs/langchain/langchain/chains/sql_database/prompt.py +++ b/libs/langchain/langchain/chains/sql_database/prompt.py @@ -1,5 +1,5 @@ # flake8: noqa -from langchain.output_parsers.list import CommaSeparatedListOutputParser +from langchain_core.output_parsers.list import CommaSeparatedListOutputParser from langchain_core.prompts.prompt import PromptTemplate diff --git a/libs/langchain/langchain/chains/summarize/__init__.py b/libs/langchain/langchain/chains/summarize/__init__.py index ab17d07952c7f..9bc8b8118bd6b 100644 --- a/libs/langchain/langchain/chains/summarize/__init__.py +++ b/libs/langchain/langchain/chains/summarize/__init__.py @@ -1,10 +1,10 @@ """Load summarizing chains.""" from typing import Any, Mapping, Optional, Protocol +from langchain_core.callbacks import Callbacks from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate -from langchain.callbacks.manager import Callbacks from langchain.chains.combine_documents.base import BaseCombineDocumentsChain from langchain.chains.combine_documents.map_reduce import MapReduceDocumentsChain from langchain.chains.combine_documents.reduce import ReduceDocumentsChain diff --git a/libs/langchain/langchain/chains/transform.py b/libs/langchain/langchain/chains/transform.py index e251cff2c931a..17a51b205c5ce 100644 --- a/libs/langchain/langchain/chains/transform.py +++ b/libs/langchain/langchain/chains/transform.py @@ -3,12 +3,12 @@ import logging from typing import Any, Awaitable, Callable, Dict, List, Optional -from langchain_core.pydantic_v1 import Field - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManagerForChainRun, CallbackManagerForChainRun, ) +from langchain_core.pydantic_v1 import Field + from langchain.chains.base import Chain logger = logging.getLogger(__name__) From 9e9ad9b0e9ffa0cf0c76cf84e1723fb843c0e665 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Wed, 17 Jan 2024 10:01:06 -0800 Subject: [PATCH 061/215] langchain[patch]: updated `retrievers` imports (#16062) Updated imports into `langchain` to `core` where it is possible --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- .../langchain/retrievers/contextual_compression.py | 8 ++++---- libs/langchain/langchain/retrievers/ensemble.py | 13 ++++++------- .../langchain/retrievers/merger_retriever.py | 7 +++---- libs/langchain/langchain/retrievers/multi_query.py | 8 ++++---- libs/langchain/langchain/retrievers/multi_vector.py | 2 +- libs/langchain/langchain/retrievers/re_phraser.py | 8 ++++---- .../langchain/retrievers/time_weighted_retriever.py | 3 +-- libs/langchain/langchain/retrievers/web_research.py | 8 ++++---- 8 files changed, 27 insertions(+), 30 deletions(-) diff --git a/libs/langchain/langchain/retrievers/contextual_compression.py b/libs/langchain/langchain/retrievers/contextual_compression.py index d06a9b690838d..b41a82a2b419b 100644 --- a/libs/langchain/langchain/retrievers/contextual_compression.py +++ b/libs/langchain/langchain/retrievers/contextual_compression.py @@ -1,12 +1,12 @@ from typing import Any, List -from langchain_core.documents import Document -from langchain_core.retrievers import BaseRetriever - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManagerForRetrieverRun, CallbackManagerForRetrieverRun, ) +from langchain_core.documents import Document +from langchain_core.retrievers import BaseRetriever + from langchain.retrievers.document_compressors.base import ( BaseDocumentCompressor, ) diff --git a/libs/langchain/langchain/retrievers/ensemble.py b/libs/langchain/langchain/retrievers/ensemble.py index 324ede8ae279b..050cb88fc4c4e 100644 --- a/libs/langchain/langchain/retrievers/ensemble.py +++ b/libs/langchain/langchain/retrievers/ensemble.py @@ -5,6 +5,10 @@ import asyncio from typing import Any, Dict, List, Optional +from langchain_core.callbacks import ( + AsyncCallbackManagerForRetrieverRun, + CallbackManagerForRetrieverRun, +) from langchain_core.documents import Document from langchain_core.load.dump import dumpd from langchain_core.pydantic_v1 import root_validator @@ -16,11 +20,6 @@ get_unique_config_specs, ) -from langchain.callbacks.manager import ( - AsyncCallbackManagerForRetrieverRun, - CallbackManagerForRetrieverRun, -) - class EnsembleRetriever(BaseRetriever): """Retriever that ensembles the multiple retrievers. @@ -57,7 +56,7 @@ def set_weights(cls, values: Dict[str, Any]) -> Dict[str, Any]: def invoke( self, input: str, config: Optional[RunnableConfig] = None, **kwargs: Any ) -> List[Document]: - from langchain_core.callbacks.manager import CallbackManager + from langchain_core.callbacks import CallbackManager config = ensure_config(config) callback_manager = CallbackManager.configure( @@ -90,7 +89,7 @@ def invoke( async def ainvoke( self, input: str, config: Optional[RunnableConfig] = None, **kwargs: Any ) -> List[Document]: - from langchain_core.callbacks.manager import AsyncCallbackManager + from langchain_core.callbacks import AsyncCallbackManager config = ensure_config(config) callback_manager = AsyncCallbackManager.configure( diff --git a/libs/langchain/langchain/retrievers/merger_retriever.py b/libs/langchain/langchain/retrievers/merger_retriever.py index ab3124a6a7b4c..f5326773bcba1 100644 --- a/libs/langchain/langchain/retrievers/merger_retriever.py +++ b/libs/langchain/langchain/retrievers/merger_retriever.py @@ -1,13 +1,12 @@ import asyncio from typing import List -from langchain_core.documents import Document -from langchain_core.retrievers import BaseRetriever - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManagerForRetrieverRun, CallbackManagerForRetrieverRun, ) +from langchain_core.documents import Document +from langchain_core.retrievers import BaseRetriever class MergerRetriever(BaseRetriever): diff --git a/libs/langchain/langchain/retrievers/multi_query.py b/libs/langchain/langchain/retrievers/multi_query.py index e3160bb752a0c..7d60b2215140b 100644 --- a/libs/langchain/langchain/retrievers/multi_query.py +++ b/libs/langchain/langchain/retrievers/multi_query.py @@ -2,16 +2,16 @@ import logging from typing import List, Sequence +from langchain_core.callbacks import ( + AsyncCallbackManagerForRetrieverRun, + CallbackManagerForRetrieverRun, +) from langchain_core.documents import Document from langchain_core.language_models import BaseLLM from langchain_core.prompts.prompt import PromptTemplate from langchain_core.pydantic_v1 import BaseModel, Field from langchain_core.retrievers import BaseRetriever -from langchain.callbacks.manager import ( - AsyncCallbackManagerForRetrieverRun, - CallbackManagerForRetrieverRun, -) from langchain.chains.llm import LLMChain from langchain.output_parsers.pydantic import PydanticOutputParser diff --git a/libs/langchain/langchain/retrievers/multi_vector.py b/libs/langchain/langchain/retrievers/multi_vector.py index bfb21af80ce82..167fc5d4cb81c 100644 --- a/libs/langchain/langchain/retrievers/multi_vector.py +++ b/libs/langchain/langchain/retrievers/multi_vector.py @@ -1,13 +1,13 @@ from enum import Enum from typing import Dict, List, Optional +from langchain_core.callbacks import CallbackManagerForRetrieverRun from langchain_core.documents import Document from langchain_core.pydantic_v1 import Field, root_validator from langchain_core.retrievers import BaseRetriever from langchain_core.stores import BaseStore, ByteStore from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import CallbackManagerForRetrieverRun from langchain.storage._lc_store import create_kv_docstore diff --git a/libs/langchain/langchain/retrievers/re_phraser.py b/libs/langchain/langchain/retrievers/re_phraser.py index e4611b202f415..2ffdd45e3eea9 100644 --- a/libs/langchain/langchain/retrievers/re_phraser.py +++ b/libs/langchain/langchain/retrievers/re_phraser.py @@ -1,15 +1,15 @@ import logging from typing import List +from langchain_core.callbacks import ( + AsyncCallbackManagerForRetrieverRun, + CallbackManagerForRetrieverRun, +) from langchain_core.documents import Document from langchain_core.language_models import BaseLLM from langchain_core.prompts.prompt import PromptTemplate from langchain_core.retrievers import BaseRetriever -from langchain.callbacks.manager import ( - AsyncCallbackManagerForRetrieverRun, - CallbackManagerForRetrieverRun, -) from langchain.chains.llm import LLMChain logger = logging.getLogger(__name__) diff --git a/libs/langchain/langchain/retrievers/time_weighted_retriever.py b/libs/langchain/langchain/retrievers/time_weighted_retriever.py index 273839984d8b0..33d553a3cae70 100644 --- a/libs/langchain/langchain/retrievers/time_weighted_retriever.py +++ b/libs/langchain/langchain/retrievers/time_weighted_retriever.py @@ -2,13 +2,12 @@ from copy import deepcopy from typing import Any, Dict, List, Optional, Tuple +from langchain_core.callbacks import CallbackManagerForRetrieverRun from langchain_core.documents import Document from langchain_core.pydantic_v1 import Field from langchain_core.retrievers import BaseRetriever from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import CallbackManagerForRetrieverRun - def _get_hours_passed(time: datetime.datetime, ref_time: datetime.datetime) -> float: """Get the hours passed between two datetimes.""" diff --git a/libs/langchain/langchain/retrievers/web_research.py b/libs/langchain/langchain/retrievers/web_research.py index 92d790cc85861..149ef247af730 100644 --- a/libs/langchain/langchain/retrievers/web_research.py +++ b/libs/langchain/langchain/retrievers/web_research.py @@ -6,6 +6,10 @@ from langchain_community.document_transformers import Html2TextTransformer from langchain_community.llms import LlamaCpp from langchain_community.utilities import GoogleSearchAPIWrapper +from langchain_core.callbacks import ( + AsyncCallbackManagerForRetrieverRun, + CallbackManagerForRetrieverRun, +) from langchain_core.documents import Document from langchain_core.language_models import BaseLLM from langchain_core.prompts import BasePromptTemplate, PromptTemplate @@ -13,10 +17,6 @@ from langchain_core.retrievers import BaseRetriever from langchain_core.vectorstores import VectorStore -from langchain.callbacks.manager import ( - AsyncCallbackManagerForRetrieverRun, - CallbackManagerForRetrieverRun, -) from langchain.chains import LLMChain from langchain.chains.prompt_selector import ConditionalPromptSelector from langchain.output_parsers.pydantic import PydanticOutputParser From 60b1bd02d7b46b5224865bbd77ee21d98c8ca4b7 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Wed, 17 Jan 2024 10:02:12 -0800 Subject: [PATCH 062/215] langchain[patch]: updated imports for `output_parsers` (#16059) Updated imports from `langchain` to `core` where it is possible --- libs/langchain/langchain/output_parsers/datetime.py | 3 +-- libs/langchain/langchain/output_parsers/openai_functions.py | 3 +-- libs/langchain/langchain/output_parsers/pandas_dataframe.py | 6 ++++-- libs/langchain/langchain/output_parsers/structured.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/langchain/langchain/output_parsers/datetime.py b/libs/langchain/langchain/output_parsers/datetime.py index 837f0b0b8be31..100e324c8a876 100644 --- a/libs/langchain/langchain/output_parsers/datetime.py +++ b/libs/langchain/langchain/output_parsers/datetime.py @@ -4,8 +4,7 @@ from langchain_core.exceptions import OutputParserException from langchain_core.output_parsers import BaseOutputParser - -from langchain.utils import comma_list +from langchain_core.utils import comma_list def _generate_random_datetime_strings( diff --git a/libs/langchain/langchain/output_parsers/openai_functions.py b/libs/langchain/langchain/output_parsers/openai_functions.py index 5f52d4e6b6f71..8551706c4d38c 100644 --- a/libs/langchain/langchain/output_parsers/openai_functions.py +++ b/libs/langchain/langchain/output_parsers/openai_functions.py @@ -8,11 +8,10 @@ BaseCumulativeTransformOutputParser, BaseGenerationOutputParser, ) +from langchain_core.output_parsers.json import parse_partial_json from langchain_core.outputs import ChatGeneration, Generation from langchain_core.pydantic_v1 import BaseModel, root_validator -from langchain.output_parsers.json import parse_partial_json - class OutputFunctionsParser(BaseGenerationOutputParser[Any]): """Parse an output that is one of sets of values.""" diff --git a/libs/langchain/langchain/output_parsers/pandas_dataframe.py b/libs/langchain/langchain/output_parsers/pandas_dataframe.py index 85bde591026da..4c0cb177d02a6 100644 --- a/libs/langchain/langchain/output_parsers/pandas_dataframe.py +++ b/libs/langchain/langchain/output_parsers/pandas_dataframe.py @@ -1,11 +1,13 @@ import re from typing import Any, Dict, List, Tuple, Union +from langchain_core.exceptions import OutputParserException +from langchain_core.output_parsers.base import BaseOutputParser +from langchain_core.pydantic_v1 import validator + from langchain.output_parsers.format_instructions import ( PANDAS_DATAFRAME_FORMAT_INSTRUCTIONS, ) -from langchain.pydantic_v1 import validator -from langchain.schema import BaseOutputParser, OutputParserException class PandasDataFrameOutputParser(BaseOutputParser): diff --git a/libs/langchain/langchain/output_parsers/structured.py b/libs/langchain/langchain/output_parsers/structured.py index 1d1b61128fc32..1d99363bc1706 100644 --- a/libs/langchain/langchain/output_parsers/structured.py +++ b/libs/langchain/langchain/output_parsers/structured.py @@ -3,13 +3,13 @@ from typing import Any, List from langchain_core.output_parsers import BaseOutputParser +from langchain_core.output_parsers.json import parse_and_check_json_markdown from langchain_core.pydantic_v1 import BaseModel from langchain.output_parsers.format_instructions import ( STRUCTURED_FORMAT_INSTRUCTIONS, STRUCTURED_FORMAT_SIMPLE_INSTRUCTIONS, ) -from langchain.output_parsers.json import parse_and_check_json_markdown line_template = '\t"{name}": {type} // {description}' From 49aff3ea5b7138b59074615ba39113a175c5d9af Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Wed, 17 Jan 2024 10:02:29 -0800 Subject: [PATCH 063/215] langchain[patch]: updated `agents` imports (#16061) Updated imports into `langchain` to `core` where it is possible --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- libs/langchain/langchain/agents/agent.py | 16 ++++++++-------- .../langchain/langchain/agents/agent_iterator.py | 14 +++++++------- libs/langchain/langchain/agents/chat/base.py | 2 +- .../langchain/agents/conversational/base.py | 2 +- .../langchain/agents/conversational_chat/base.py | 2 +- .../agents/conversational_chat/output_parser.py | 2 +- libs/langchain/langchain/agents/initialize.py | 2 +- libs/langchain/langchain/agents/load_tools.py | 6 +++--- libs/langchain/langchain/agents/loading.py | 2 +- libs/langchain/langchain/agents/mrkl/base.py | 2 +- .../langchain/agents/openai_assistant/base.py | 3 +-- .../agents/openai_functions_agent/base.py | 3 +-- .../agents/openai_functions_multi_agent/base.py | 5 ++--- .../langchain/agents/output_parsers/json.py | 2 +- libs/langchain/langchain/agents/react/base.py | 3 +-- .../agents/self_ask_with_search/base.py | 3 +-- .../langchain/agents/structured_chat/base.py | 4 ++-- libs/langchain/langchain/agents/tools.py | 5 ++--- libs/langchain/langchain/agents/xml/base.py | 2 +- 19 files changed, 37 insertions(+), 43 deletions(-) diff --git a/libs/langchain/langchain/agents/agent.py b/libs/langchain/langchain/agents/agent.py index 8fbdf976a1155..4bfcde68e0c83 100644 --- a/libs/langchain/langchain/agents/agent.py +++ b/libs/langchain/langchain/agents/agent.py @@ -23,6 +23,14 @@ import yaml from langchain_core._api import deprecated from langchain_core.agents import AgentAction, AgentFinish, AgentStep +from langchain_core.callbacks import ( + AsyncCallbackManagerForChainRun, + AsyncCallbackManagerForToolRun, + BaseCallbackManager, + CallbackManagerForChainRun, + CallbackManagerForToolRun, + Callbacks, +) from langchain_core.exceptions import OutputParserException from langchain_core.language_models import BaseLanguageModel from langchain_core.messages import BaseMessage @@ -39,14 +47,6 @@ from langchain.agents.agent_iterator import AgentExecutorIterator from langchain.agents.agent_types import AgentType from langchain.agents.tools import InvalidTool -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - AsyncCallbackManagerForToolRun, - CallbackManagerForChainRun, - CallbackManagerForToolRun, - Callbacks, -) from langchain.chains.base import Chain from langchain.chains.llm import LLMChain from langchain.utilities.asyncio import asyncio_timeout diff --git a/libs/langchain/langchain/agents/agent_iterator.py b/libs/langchain/langchain/agents/agent_iterator.py index 52b5bd2c1bc10..12c995f2e9708 100644 --- a/libs/langchain/langchain/agents/agent_iterator.py +++ b/libs/langchain/langchain/agents/agent_iterator.py @@ -20,20 +20,20 @@ AgentFinish, AgentStep, ) -from langchain_core.load.dump import dumpd -from langchain_core.outputs import RunInfo -from langchain_core.runnables.utils import AddableDict -from langchain_core.utils.input import get_color_mapping - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManager, AsyncCallbackManagerForChainRun, CallbackManager, CallbackManagerForChainRun, Callbacks, ) +from langchain_core.load.dump import dumpd +from langchain_core.outputs import RunInfo +from langchain_core.runnables.utils import AddableDict +from langchain_core.tools import BaseTool +from langchain_core.utils.input import get_color_mapping + from langchain.schema import RUN_KEY -from langchain.tools import BaseTool from langchain.utilities.asyncio import asyncio_timeout if TYPE_CHECKING: diff --git a/libs/langchain/langchain/agents/chat/base.py b/libs/langchain/langchain/agents/chat/base.py index 00a320a543ce1..ffd36852e1eff 100644 --- a/libs/langchain/langchain/agents/chat/base.py +++ b/libs/langchain/langchain/agents/chat/base.py @@ -2,6 +2,7 @@ from langchain_core._api import deprecated from langchain_core.agents import AgentAction +from langchain_core.callbacks import BaseCallbackManager from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.prompts.chat import ( @@ -21,7 +22,6 @@ SYSTEM_MESSAGE_SUFFIX, ) from langchain.agents.utils import validate_tools_single_input -from langchain.callbacks.base import BaseCallbackManager from langchain.chains.llm import LLMChain diff --git a/libs/langchain/langchain/agents/conversational/base.py b/libs/langchain/langchain/agents/conversational/base.py index fcc03ad1be353..11e2fc12470bf 100644 --- a/libs/langchain/langchain/agents/conversational/base.py +++ b/libs/langchain/langchain/agents/conversational/base.py @@ -4,6 +4,7 @@ from typing import Any, List, Optional, Sequence from langchain_core._api import deprecated +from langchain_core.callbacks import BaseCallbackManager from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import PromptTemplate from langchain_core.pydantic_v1 import Field @@ -14,7 +15,6 @@ from langchain.agents.conversational.output_parser import ConvoOutputParser from langchain.agents.conversational.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.agents.utils import validate_tools_single_input -from langchain.callbacks.base import BaseCallbackManager from langchain.chains import LLMChain diff --git a/libs/langchain/langchain/agents/conversational_chat/base.py b/libs/langchain/langchain/agents/conversational_chat/base.py index 00f0137cda0b2..8ee2218780bdc 100644 --- a/libs/langchain/langchain/agents/conversational_chat/base.py +++ b/libs/langchain/langchain/agents/conversational_chat/base.py @@ -5,6 +5,7 @@ from langchain_core._api import deprecated from langchain_core.agents import AgentAction +from langchain_core.callbacks import BaseCallbackManager from langchain_core.language_models import BaseLanguageModel from langchain_core.messages import AIMessage, BaseMessage, HumanMessage from langchain_core.output_parsers import BaseOutputParser @@ -26,7 +27,6 @@ TEMPLATE_TOOL_RESPONSE, ) from langchain.agents.utils import validate_tools_single_input -from langchain.callbacks.base import BaseCallbackManager from langchain.chains import LLMChain diff --git a/libs/langchain/langchain/agents/conversational_chat/output_parser.py b/libs/langchain/langchain/agents/conversational_chat/output_parser.py index 4265ee553341a..46ccce6d57189 100644 --- a/libs/langchain/langchain/agents/conversational_chat/output_parser.py +++ b/libs/langchain/langchain/agents/conversational_chat/output_parser.py @@ -4,10 +4,10 @@ from langchain_core.agents import AgentAction, AgentFinish from langchain_core.exceptions import OutputParserException +from langchain_core.output_parsers.json import parse_json_markdown from langchain.agents import AgentOutputParser from langchain.agents.conversational_chat.prompt import FORMAT_INSTRUCTIONS -from langchain.output_parsers.json import parse_json_markdown # Define a class that parses output for conversational agents diff --git a/libs/langchain/langchain/agents/initialize.py b/libs/langchain/langchain/agents/initialize.py index 65cfbd200398a..890bc90e68f01 100644 --- a/libs/langchain/langchain/agents/initialize.py +++ b/libs/langchain/langchain/agents/initialize.py @@ -2,13 +2,13 @@ from typing import Any, Optional, Sequence from langchain_core._api import deprecated +from langchain_core.callbacks import BaseCallbackManager from langchain_core.language_models import BaseLanguageModel from langchain_core.tools import BaseTool from langchain.agents.agent import AgentExecutor from langchain.agents.agent_types import AgentType from langchain.agents.loading import AGENT_TO_CLASS, load_agent -from langchain.callbacks.base import BaseCallbackManager @deprecated( diff --git a/libs/langchain/langchain/agents/load_tools.py b/libs/langchain/langchain/agents/load_tools.py index 192048b9fc119..ab3a34cfee72d 100644 --- a/libs/langchain/langchain/agents/load_tools.py +++ b/libs/langchain/langchain/agents/load_tools.py @@ -18,10 +18,10 @@ from typing import Any, Dict, List, Optional, Callable, Tuple from mypy_extensions import Arg, KwArg -from langchain.agents.tools import Tool +from langchain_core.tools import Tool from langchain_core.language_models import BaseLanguageModel -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import Callbacks +from langchain_core.callbacks import BaseCallbackManager +from langchain_core.callbacks import Callbacks from langchain.chains.api import news_docs, open_meteo_docs, podcast_docs, tmdb_docs from langchain.chains.api.base import APIChain from langchain.chains.llm_math.base import LLMMathChain diff --git a/libs/langchain/langchain/agents/loading.py b/libs/langchain/langchain/agents/loading.py index 28540fcfd4a16..e1d9747df2d5f 100644 --- a/libs/langchain/langchain/agents/loading.py +++ b/libs/langchain/langchain/agents/loading.py @@ -7,10 +7,10 @@ import yaml from langchain_core._api import deprecated from langchain_core.language_models import BaseLanguageModel +from langchain_core.tools import Tool from langchain_core.utils.loading import try_load_from_hub from langchain.agents.agent import BaseMultiActionAgent, BaseSingleActionAgent -from langchain.agents.tools import Tool from langchain.agents.types import AGENT_TO_CLASS from langchain.chains.loading import load_chain, load_chain_from_config diff --git a/libs/langchain/langchain/agents/mrkl/base.py b/libs/langchain/langchain/agents/mrkl/base.py index 51e3ef2dbae3c..406390d3f8ed7 100644 --- a/libs/langchain/langchain/agents/mrkl/base.py +++ b/libs/langchain/langchain/agents/mrkl/base.py @@ -4,6 +4,7 @@ from typing import Any, Callable, List, NamedTuple, Optional, Sequence from langchain_core._api import deprecated +from langchain_core.callbacks import BaseCallbackManager from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import PromptTemplate from langchain_core.pydantic_v1 import Field @@ -15,7 +16,6 @@ from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.agents.tools import Tool from langchain.agents.utils import validate_tools_single_input -from langchain.callbacks.base import BaseCallbackManager from langchain.chains import LLMChain diff --git a/libs/langchain/langchain/agents/openai_assistant/base.py b/libs/langchain/langchain/agents/openai_assistant/base.py index 498957ad8bcac..84d99c8f97e97 100644 --- a/libs/langchain/langchain/agents/openai_assistant/base.py +++ b/libs/langchain/langchain/agents/openai_assistant/base.py @@ -7,13 +7,12 @@ from langchain_community.tools.convert_to_openai import format_tool_to_openai_tool from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.callbacks import CallbackManager from langchain_core.load import dumpd from langchain_core.pydantic_v1 import Field from langchain_core.runnables import RunnableConfig, RunnableSerializable, ensure_config from langchain_core.tools import BaseTool -from langchain.callbacks.manager import CallbackManager - if TYPE_CHECKING: import openai from openai.types.beta.threads import ThreadMessage diff --git a/libs/langchain/langchain/agents/openai_functions_agent/base.py b/libs/langchain/langchain/agents/openai_functions_agent/base.py index 1d486dc685631..e0180693202e5 100644 --- a/libs/langchain/langchain/agents/openai_functions_agent/base.py +++ b/libs/langchain/langchain/agents/openai_functions_agent/base.py @@ -4,6 +4,7 @@ from langchain_community.tools.convert_to_openai import format_tool_to_openai_function from langchain_core._api import deprecated from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.callbacks import BaseCallbackManager, Callbacks from langchain_core.language_models import BaseLanguageModel from langchain_core.messages import ( BaseMessage, @@ -27,8 +28,6 @@ from langchain.agents.output_parsers.openai_functions import ( OpenAIFunctionsAgentOutputParser, ) -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import Callbacks @deprecated("0.1.0", alternative="create_openai_functions_agent", removal="0.2.0") diff --git a/libs/langchain/langchain/agents/openai_functions_multi_agent/base.py b/libs/langchain/langchain/agents/openai_functions_multi_agent/base.py index 168f852c591c6..03d804c60fd6b 100644 --- a/libs/langchain/langchain/agents/openai_functions_multi_agent/base.py +++ b/libs/langchain/langchain/agents/openai_functions_multi_agent/base.py @@ -5,6 +5,7 @@ from langchain_core._api import deprecated from langchain_core.agents import AgentAction, AgentActionMessageLog, AgentFinish +from langchain_core.callbacks import BaseCallbackManager, Callbacks from langchain_core.exceptions import OutputParserException from langchain_core.language_models import BaseLanguageModel from langchain_core.messages import ( @@ -20,14 +21,12 @@ MessagesPlaceholder, ) from langchain_core.pydantic_v1 import root_validator +from langchain_core.tools import BaseTool from langchain.agents import BaseMultiActionAgent from langchain.agents.format_scratchpad.openai_functions import ( format_to_openai_function_messages, ) -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import Callbacks -from langchain.tools import BaseTool # For backwards compatibility _FunctionsAgentAction = AgentActionMessageLog diff --git a/libs/langchain/langchain/agents/output_parsers/json.py b/libs/langchain/langchain/agents/output_parsers/json.py index 5fa543ea9dfea..2dbc56178ff0a 100644 --- a/libs/langchain/langchain/agents/output_parsers/json.py +++ b/libs/langchain/langchain/agents/output_parsers/json.py @@ -5,9 +5,9 @@ from langchain_core.agents import AgentAction, AgentFinish from langchain_core.exceptions import OutputParserException +from langchain_core.output_parsers.json import parse_json_markdown from langchain.agents.agent import AgentOutputParser -from langchain.output_parsers.json import parse_json_markdown logger = logging.getLogger(__name__) diff --git a/libs/langchain/langchain/agents/react/base.py b/libs/langchain/langchain/agents/react/base.py index fba742ac458f6..437eb80e72087 100644 --- a/libs/langchain/langchain/agents/react/base.py +++ b/libs/langchain/langchain/agents/react/base.py @@ -6,14 +6,13 @@ from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field -from langchain_core.tools import BaseTool +from langchain_core.tools import BaseTool, Tool from langchain.agents.agent import Agent, AgentExecutor, AgentOutputParser from langchain.agents.agent_types import AgentType from langchain.agents.react.output_parser import ReActOutputParser from langchain.agents.react.textworld_prompt import TEXTWORLD_PROMPT from langchain.agents.react.wiki_prompt import WIKI_PROMPT -from langchain.agents.tools import Tool from langchain.agents.utils import validate_tools_single_input from langchain.docstore.base import Docstore diff --git a/libs/langchain/langchain/agents/self_ask_with_search/base.py b/libs/langchain/langchain/agents/self_ask_with_search/base.py index 9df14cb433574..d7526f4c5486f 100644 --- a/libs/langchain/langchain/agents/self_ask_with_search/base.py +++ b/libs/langchain/langchain/agents/self_ask_with_search/base.py @@ -9,14 +9,13 @@ from langchain_core.prompts import BasePromptTemplate from langchain_core.pydantic_v1 import Field from langchain_core.runnables import Runnable, RunnablePassthrough -from langchain_core.tools import BaseTool +from langchain_core.tools import BaseTool, Tool from langchain.agents.agent import Agent, AgentExecutor, AgentOutputParser from langchain.agents.agent_types import AgentType from langchain.agents.format_scratchpad import format_log_to_str from langchain.agents.self_ask_with_search.output_parser import SelfAskOutputParser from langchain.agents.self_ask_with_search.prompt import PROMPT -from langchain.agents.tools import Tool from langchain.agents.utils import validate_tools_single_input diff --git a/libs/langchain/langchain/agents/structured_chat/base.py b/libs/langchain/langchain/agents/structured_chat/base.py index 53e11b33c01d8..ef037860d3c46 100644 --- a/libs/langchain/langchain/agents/structured_chat/base.py +++ b/libs/langchain/langchain/agents/structured_chat/base.py @@ -3,6 +3,7 @@ from langchain_core._api import deprecated from langchain_core.agents import AgentAction +from langchain_core.callbacks import BaseCallbackManager from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate from langchain_core.prompts.chat import ( @@ -12,6 +13,7 @@ ) from langchain_core.pydantic_v1 import Field from langchain_core.runnables import Runnable, RunnablePassthrough +from langchain_core.tools import BaseTool from langchain.agents.agent import Agent, AgentOutputParser from langchain.agents.format_scratchpad import format_log_to_str @@ -20,9 +22,7 @@ StructuredChatOutputParserWithRetries, ) from langchain.agents.structured_chat.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX -from langchain.callbacks.base import BaseCallbackManager from langchain.chains.llm import LLMChain -from langchain.tools import BaseTool from langchain.tools.render import render_text_description_and_args HUMAN_MESSAGE_TEMPLATE = "{input}\n\n{agent_scratchpad}" diff --git a/libs/langchain/langchain/agents/tools.py b/libs/langchain/langchain/agents/tools.py index aaf360afbc5d5..e804ab25025e2 100644 --- a/libs/langchain/langchain/agents/tools.py +++ b/libs/langchain/langchain/agents/tools.py @@ -1,12 +1,11 @@ """Interface for tools.""" from typing import List, Optional -from langchain_core.tools import BaseTool, Tool, tool - -from langchain.callbacks.manager import ( +from langchain_core.callbacks import ( AsyncCallbackManagerForToolRun, CallbackManagerForToolRun, ) +from langchain_core.tools import BaseTool, Tool, tool class InvalidTool(BaseTool): diff --git a/libs/langchain/langchain/agents/xml/base.py b/libs/langchain/langchain/agents/xml/base.py index bd678979f746d..be85a7b9738ff 100644 --- a/libs/langchain/langchain/agents/xml/base.py +++ b/libs/langchain/langchain/agents/xml/base.py @@ -2,6 +2,7 @@ from langchain_core._api import deprecated from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.callbacks import Callbacks from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts.base import BasePromptTemplate from langchain_core.prompts.chat import AIMessagePromptTemplate, ChatPromptTemplate @@ -12,7 +13,6 @@ from langchain.agents.format_scratchpad import format_xml from langchain.agents.output_parsers import XMLAgentOutputParser from langchain.agents.xml.prompt import agent_instructions -from langchain.callbacks.base import Callbacks from langchain.chains.llm import LLMChain from langchain.tools.render import render_text_description From e7ddec1f2c8a06e6349c825b9af4c3d6ad254733 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:04:34 -0800 Subject: [PATCH 064/215] docs: change parallel doc name (#16152) --- docs/docs/use_cases/tool_use/parallel.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use_cases/tool_use/parallel.ipynb b/docs/docs/use_cases/tool_use/parallel.ipynb index 241e1582c3d95..0220fb491e0b8 100644 --- a/docs/docs/use_cases/tool_use/parallel.ipynb +++ b/docs/docs/use_cases/tool_use/parallel.ipynb @@ -5,7 +5,7 @@ "id": "95982bf1-7d9d-4dd6-a4ad-9de0719fe17f", "metadata": {}, "source": [ - "# Chains with parallel tool use\n", + "# Parallel tool use\n", "\n", "In the [Chains with multiple tools](/docs/use_cases/tool_use/multiple_tools) guide we saw how to build function-calling chains that select between multiple tools. Some models, like the OpenAI models released in Fall 2023, also support parallel function calling, which allows you to invoke multiple functions (or the same function multiple times) in a single model call. Our previous chain from the multiple tools guides actually already supports this, we just need to use an OpenAI model capable of parallel function calling." ] From c5f6b828ad5c13db429c05035794829247be8621 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Wed, 17 Jan 2024 10:06:18 -0800 Subject: [PATCH 065/215] langchain[patch], community[minor]: move `output_parsers.ernie_functions` (#16057) `output_parsers.ernie_functions` moved into `community` --- .../output_parsers/ernie_functions.py | 179 ++++++++++++++++ .../output_parsers/ernie_functions.py | 191 ++---------------- 2 files changed, 192 insertions(+), 178 deletions(-) create mode 100644 libs/community/langchain_community/output_parsers/ernie_functions.py diff --git a/libs/community/langchain_community/output_parsers/ernie_functions.py b/libs/community/langchain_community/output_parsers/ernie_functions.py new file mode 100644 index 0000000000000..223284649f3b4 --- /dev/null +++ b/libs/community/langchain_community/output_parsers/ernie_functions.py @@ -0,0 +1,179 @@ +import copy +import json +from typing import Any, Dict, List, Optional, Type, Union + +import jsonpatch +from langchain_core.exceptions import OutputParserException +from langchain_core.output_parsers import ( + BaseCumulativeTransformOutputParser, + BaseGenerationOutputParser, +) +from langchain_core.output_parsers.json import parse_partial_json +from langchain_core.outputs.chat_generation import ( + ChatGeneration, + Generation, +) +from langchain_core.pydantic_v1 import BaseModel, root_validator + + +class OutputFunctionsParser(BaseGenerationOutputParser[Any]): + """Parse an output that is one of sets of values.""" + + args_only: bool = True + """Whether to only return the arguments to the function call.""" + + def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: + generation = result[0] + if not isinstance(generation, ChatGeneration): + raise OutputParserException( + "This output parser can only be used with a chat generation." + ) + message = generation.message + try: + func_call = copy.deepcopy(message.additional_kwargs["function_call"]) + except KeyError as exc: + raise OutputParserException(f"Could not parse function call: {exc}") + + if self.args_only: + return func_call["arguments"] + return func_call + + +class JsonOutputFunctionsParser(BaseCumulativeTransformOutputParser[Any]): + """Parse an output as the Json object.""" + + strict: bool = False + """Whether to allow non-JSON-compliant strings. + + See: https://docs.python.org/3/library/json.html#encoders-and-decoders + + Useful when the parsed output may include unicode characters or new lines. + """ + + args_only: bool = True + """Whether to only return the arguments to the function call.""" + + @property + def _type(self) -> str: + return "json_functions" + + def _diff(self, prev: Optional[Any], next: Any) -> Any: + return jsonpatch.make_patch(prev, next).patch + + def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: + if len(result) != 1: + raise OutputParserException( + f"Expected exactly one result, but got {len(result)}" + ) + generation = result[0] + if not isinstance(generation, ChatGeneration): + raise OutputParserException( + "This output parser can only be used with a chat generation." + ) + message = generation.message + if "function_call" not in message.additional_kwargs: + return None + try: + function_call = message.additional_kwargs["function_call"] + except KeyError as exc: + if partial: + return None + else: + raise OutputParserException(f"Could not parse function call: {exc}") + try: + if partial: + if self.args_only: + return parse_partial_json( + function_call["arguments"], strict=self.strict + ) + else: + return { + **function_call, + "arguments": parse_partial_json( + function_call["arguments"], strict=self.strict + ), + } + else: + if self.args_only: + try: + return json.loads( + function_call["arguments"], strict=self.strict + ) + except (json.JSONDecodeError, TypeError) as exc: + raise OutputParserException( + f"Could not parse function call data: {exc}" + ) + else: + try: + return { + **function_call, + "arguments": json.loads( + function_call["arguments"], strict=self.strict + ), + } + except (json.JSONDecodeError, TypeError) as exc: + raise OutputParserException( + f"Could not parse function call data: {exc}" + ) + except KeyError: + return None + + # This method would be called by the default implementation of `parse_result` + # but we're overriding that method so it's not needed. + def parse(self, text: str) -> Any: + raise NotImplementedError() + + +class JsonKeyOutputFunctionsParser(JsonOutputFunctionsParser): + """Parse an output as the element of the Json object.""" + + key_name: str + """The name of the key to return.""" + + def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: + res = super().parse_result(result, partial=partial) + if partial and res is None: + return None + return res.get(self.key_name) if partial else res[self.key_name] + + +class PydanticOutputFunctionsParser(OutputFunctionsParser): + """Parse an output as a pydantic object.""" + + pydantic_schema: Union[Type[BaseModel], Dict[str, Type[BaseModel]]] + """The pydantic schema to parse the output with.""" + + @root_validator(pre=True) + def validate_schema(cls, values: Dict) -> Dict: + schema = values["pydantic_schema"] + if "args_only" not in values: + values["args_only"] = isinstance(schema, type) and issubclass( + schema, BaseModel + ) + elif values["args_only"] and isinstance(schema, Dict): + raise ValueError( + "If multiple pydantic schemas are provided then args_only should be" + " False." + ) + return values + + def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: + _result = super().parse_result(result) + if self.args_only: + pydantic_args = self.pydantic_schema.parse_raw(_result) # type: ignore + else: + fn_name = _result["name"] + _args = _result["arguments"] + pydantic_args = self.pydantic_schema[fn_name].parse_raw(_args) # type: ignore # noqa: E501 + return pydantic_args + + +class PydanticAttrOutputFunctionsParser(PydanticOutputFunctionsParser): + """Parse an output as an attribute of a pydantic object.""" + + attr_name: str + """The name of the attribute to return.""" + + def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: + result = super().parse_result(result) + return getattr(result, self.attr_name) diff --git a/libs/langchain/langchain/output_parsers/ernie_functions.py b/libs/langchain/langchain/output_parsers/ernie_functions.py index 0bd481fa8c592..c258f854ef51f 100644 --- a/libs/langchain/langchain/output_parsers/ernie_functions.py +++ b/libs/langchain/langchain/output_parsers/ernie_functions.py @@ -1,180 +1,15 @@ -import copy -import json -from typing import Any, Dict, List, Optional, Type, Union - -import jsonpatch -from langchain_core.output_parsers import ( - BaseCumulativeTransformOutputParser, - BaseGenerationOutputParser, -) - -from langchain.output_parsers.json import parse_partial_json -from langchain.pydantic_v1 import BaseModel, root_validator -from langchain.schema import ( - ChatGeneration, - Generation, - OutputParserException, +from langchain_community.output_parsers.ernie_functions import ( + JsonKeyOutputFunctionsParser, + JsonOutputFunctionsParser, + OutputFunctionsParser, + PydanticAttrOutputFunctionsParser, + PydanticOutputFunctionsParser, ) - -class OutputFunctionsParser(BaseGenerationOutputParser[Any]): - """Parse an output that is one of sets of values.""" - - args_only: bool = True - """Whether to only return the arguments to the function call.""" - - def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: - generation = result[0] - if not isinstance(generation, ChatGeneration): - raise OutputParserException( - "This output parser can only be used with a chat generation." - ) - message = generation.message - try: - func_call = copy.deepcopy(message.additional_kwargs["function_call"]) - except KeyError as exc: - raise OutputParserException(f"Could not parse function call: {exc}") - - if self.args_only: - return func_call["arguments"] - return func_call - - -class JsonOutputFunctionsParser(BaseCumulativeTransformOutputParser[Any]): - """Parse an output as the Json object.""" - - strict: bool = False - """Whether to allow non-JSON-compliant strings. - - See: https://docs.python.org/3/library/json.html#encoders-and-decoders - - Useful when the parsed output may include unicode characters or new lines. - """ - - args_only: bool = True - """Whether to only return the arguments to the function call.""" - - @property - def _type(self) -> str: - return "json_functions" - - def _diff(self, prev: Optional[Any], next: Any) -> Any: - return jsonpatch.make_patch(prev, next).patch - - def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: - if len(result) != 1: - raise OutputParserException( - f"Expected exactly one result, but got {len(result)}" - ) - generation = result[0] - if not isinstance(generation, ChatGeneration): - raise OutputParserException( - "This output parser can only be used with a chat generation." - ) - message = generation.message - if "function_call" not in message.additional_kwargs: - return None - try: - function_call = message.additional_kwargs["function_call"] - except KeyError as exc: - if partial: - return None - else: - raise OutputParserException(f"Could not parse function call: {exc}") - try: - if partial: - if self.args_only: - return parse_partial_json( - function_call["arguments"], strict=self.strict - ) - else: - return { - **function_call, - "arguments": parse_partial_json( - function_call["arguments"], strict=self.strict - ), - } - else: - if self.args_only: - try: - return json.loads( - function_call["arguments"], strict=self.strict - ) - except (json.JSONDecodeError, TypeError) as exc: - raise OutputParserException( - f"Could not parse function call data: {exc}" - ) - else: - try: - return { - **function_call, - "arguments": json.loads( - function_call["arguments"], strict=self.strict - ), - } - except (json.JSONDecodeError, TypeError) as exc: - raise OutputParserException( - f"Could not parse function call data: {exc}" - ) - except KeyError: - return None - - # This method would be called by the default implementation of `parse_result` - # but we're overriding that method so it's not needed. - def parse(self, text: str) -> Any: - raise NotImplementedError() - - -class JsonKeyOutputFunctionsParser(JsonOutputFunctionsParser): - """Parse an output as the element of the Json object.""" - - key_name: str - """The name of the key to return.""" - - def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: - res = super().parse_result(result, partial=partial) - if partial and res is None: - return None - return res.get(self.key_name) if partial else res[self.key_name] - - -class PydanticOutputFunctionsParser(OutputFunctionsParser): - """Parse an output as a pydantic object.""" - - pydantic_schema: Union[Type[BaseModel], Dict[str, Type[BaseModel]]] - """The pydantic schema to parse the output with.""" - - @root_validator(pre=True) - def validate_schema(cls, values: Dict) -> Dict: - schema = values["pydantic_schema"] - if "args_only" not in values: - values["args_only"] = isinstance(schema, type) and issubclass( - schema, BaseModel - ) - elif values["args_only"] and isinstance(schema, Dict): - raise ValueError( - "If multiple pydantic schemas are provided then args_only should be" - " False." - ) - return values - - def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: - _result = super().parse_result(result) - if self.args_only: - pydantic_args = self.pydantic_schema.parse_raw(_result) # type: ignore - else: - fn_name = _result["name"] - _args = _result["arguments"] - pydantic_args = self.pydantic_schema[fn_name].parse_raw(_args) # type: ignore # noqa: E501 - return pydantic_args - - -class PydanticAttrOutputFunctionsParser(PydanticOutputFunctionsParser): - """Parse an output as an attribute of a pydantic object.""" - - attr_name: str - """The name of the attribute to return.""" - - def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any: - result = super().parse_result(result) - return getattr(result, self.attr_name) +__all__ = [ + "JsonKeyOutputFunctionsParser", + "JsonOutputFunctionsParser", + "OutputFunctionsParser", + "PydanticAttrOutputFunctionsParser", + "PydanticOutputFunctionsParser", +] From 2709d3e5f2f8b8085f87434f44ba6ff095193c84 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline <leo.gan.57@gmail.com> Date: Wed, 17 Jan 2024 10:06:59 -0800 Subject: [PATCH 066/215] langchain[patch]: updated imports for `langchain.callbacks` (#16060) Updated imports from 'langchain` to `core` where it is possible --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- libs/langchain/langchain/callbacks/base.py | 2 +- libs/langchain/langchain/callbacks/file.py | 3 +-- libs/langchain/langchain/callbacks/streaming_aiter.py | 3 +-- libs/langchain/langchain/callbacks/streaming_stdout.py | 2 +- .../langchain/callbacks/streaming_stdout_final_only.py | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libs/langchain/langchain/callbacks/base.py b/libs/langchain/langchain/callbacks/base.py index 9b9d189d3f612..7eaedce6fc89c 100644 --- a/libs/langchain/langchain/callbacks/base.py +++ b/libs/langchain/langchain/callbacks/base.py @@ -1,7 +1,7 @@ """Base callback handler that can be used to handle callbacks in langchain.""" from __future__ import annotations -from langchain_core.callbacks.base import ( +from langchain_core.callbacks import ( AsyncCallbackHandler, BaseCallbackHandler, BaseCallbackManager, diff --git a/libs/langchain/langchain/callbacks/file.py b/libs/langchain/langchain/callbacks/file.py index 9768a9f031604..06bcecb027d0d 100644 --- a/libs/langchain/langchain/callbacks/file.py +++ b/libs/langchain/langchain/callbacks/file.py @@ -2,10 +2,9 @@ from typing import Any, Dict, Optional, TextIO, cast from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.callbacks import BaseCallbackHandler from langchain_core.utils.input import print_text -from langchain.callbacks.base import BaseCallbackHandler - class FileCallbackHandler(BaseCallbackHandler): """Callback Handler that writes to a file.""" diff --git a/libs/langchain/langchain/callbacks/streaming_aiter.py b/libs/langchain/langchain/callbacks/streaming_aiter.py index 92218af87bbc2..2df5849db8f77 100644 --- a/libs/langchain/langchain/callbacks/streaming_aiter.py +++ b/libs/langchain/langchain/callbacks/streaming_aiter.py @@ -3,10 +3,9 @@ import asyncio from typing import Any, AsyncIterator, Dict, List, Literal, Union, cast +from langchain_core.callbacks import AsyncCallbackHandler from langchain_core.outputs import LLMResult -from langchain.callbacks.base import AsyncCallbackHandler - # TODO If used by two LLM runs in parallel this won't work as expected diff --git a/libs/langchain/langchain/callbacks/streaming_stdout.py b/libs/langchain/langchain/callbacks/streaming_stdout.py index e2a22232b5798..1870f79210aa3 100644 --- a/libs/langchain/langchain/callbacks/streaming_stdout.py +++ b/libs/langchain/langchain/callbacks/streaming_stdout.py @@ -1,4 +1,4 @@ """Callback Handler streams to stdout on new llm token.""" -from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler +from langchain_core.callbacks import StreamingStdOutCallbackHandler __all__ = ["StreamingStdOutCallbackHandler"] diff --git a/libs/langchain/langchain/callbacks/streaming_stdout_final_only.py b/libs/langchain/langchain/callbacks/streaming_stdout_final_only.py index 6f3593d4d7335..2dce9266a7b29 100644 --- a/libs/langchain/langchain/callbacks/streaming_stdout_final_only.py +++ b/libs/langchain/langchain/callbacks/streaming_stdout_final_only.py @@ -2,7 +2,7 @@ import sys from typing import Any, Dict, List, Optional -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler +from langchain_core.callbacks import StreamingStdOutCallbackHandler DEFAULT_ANSWER_PREFIX_TOKENS = ["Final", "Answer", ":"] From 11327e6b64d2703436aae75c6f4097e0412aa325 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Wed, 17 Jan 2024 10:16:59 -0800 Subject: [PATCH 067/215] google-vertexai[patch]: typing, release 0.0.2 (#16153) --- libs/partners/google-vertexai/poetry.lock | 13 ++++++++++++- libs/partners/google-vertexai/pyproject.toml | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libs/partners/google-vertexai/poetry.lock b/libs/partners/google-vertexai/poetry.lock index 4665a3f425a4f..4c55611ca7920 100644 --- a/libs/partners/google-vertexai/poetry.lock +++ b/libs/partners/google-vertexai/poetry.lock @@ -2020,6 +2020,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-google-cloud-ndb" +version = "2.2.0.20240106" +description = "Typing stubs for google-cloud-ndb" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-google-cloud-ndb-2.2.0.20240106.tar.gz", hash = "sha256:b81d4ea35f212dc845429d08f1981eb011fe78cee3eebba81157d18b7f6e4616"}, + {file = "types_google_cloud_ndb-2.2.0.20240106-py3-none-any.whl", hash = "sha256:c76efa97b17c15865784fb4e54da56cad805acf81f908dfe4f962a957cb84555"}, +] + [[package]] name = "types-protobuf" version = "4.24.0.4" @@ -2243,4 +2254,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "a37b4d8ff76ba8de92cffb3d3ae396b82be8faf03f9a78bd6d96d01fb915b22c" +content-hash = "f86059ed812a97d68a0a9715c2f6d9f7687b5c07685ae224a63ff35f06e55a70" diff --git a/libs/partners/google-vertexai/pyproject.toml b/libs/partners/google-vertexai/pyproject.toml index e1ce09d46cd4d..41bcffb357144 100644 --- a/libs/partners/google-vertexai/pyproject.toml +++ b/libs/partners/google-vertexai/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-google-vertexai" -version = "0.0.1.post1" +version = "0.0.2" description = "An integration package connecting GoogleVertexAI and LangChain" authors = [] readme = "README.md" @@ -55,6 +55,7 @@ ruff = "^0.1.5" [tool.poetry.group.typing.dependencies] mypy = "^0.991" langchain-core = {path = "../../core", develop = true} +types-google-cloud-ndb = "^2.2.0.20240106" [tool.poetry.group.dev] optional = true From 1fa056c324ccc8bee2c07a397653e653fb68d52d Mon Sep 17 00:00:00 2001 From: Mohammad Mohtashim <45242107+keenborder786@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:31:11 +0500 Subject: [PATCH 068/215] community[patch]: Don't set search path for unknown SQL dialects (#16047) - **Description:** Made a small fix for the `SQLDatabase` highlighted in an issue. The issue pertains to switching schema for different SQL engines. - **Issue:** #16023 @baskaryan --- libs/community/langchain_community/utilities/sql_database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/utilities/sql_database.py b/libs/community/langchain_community/utilities/sql_database.py index 9f665b7132d82..8379fbc337a9b 100644 --- a/libs/community/langchain_community/utilities/sql_database.py +++ b/libs/community/langchain_community/utilities/sql_database.py @@ -409,8 +409,9 @@ def _execute( # If anybody using Sybase SQL anywhere database then it should not # go to else condition. It should be same as mssql. pass - else: # postgresql and other compatible dialects + elif self.dialect == "postgresql": # postgresql connection.exec_driver_sql("SET search_path TO %s", (self._schema,)) + cursor = connection.execute(text(command)) if cursor.returns_rows: if fetch == "all": From fb940d11df5f275bb0a82f725f76643fd9594307 Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Wed, 17 Jan 2024 19:37:07 +0100 Subject: [PATCH 069/215] community[patch]: Use newer MetadataVectorCassandraTable in Cassandra vector store (#15987) as VectorTable is deprecated Tested manually with `test_cassandra.py` vector store integration test. --- .../vectorstores/cassandra.py | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/cassandra.py b/libs/community/langchain_community/vectorstores/cassandra.py index 2a062014339a6..041f699520083 100644 --- a/libs/community/langchain_community/vectorstores/cassandra.py +++ b/libs/community/langchain_community/vectorstores/cassandra.py @@ -75,7 +75,7 @@ def __init__( ttl_seconds: Optional[int] = None, ) -> None: try: - from cassio.vector import VectorTable + from cassio.table import MetadataVectorCassandraTable except (ImportError, ModuleNotFoundError): raise ImportError( "Could not import cassio python package. " @@ -90,11 +90,12 @@ def __init__( # self._embedding_dimension = None # - self.table = VectorTable( + self.table = MetadataVectorCassandraTable( session=session, keyspace=keyspace, table=table_name, - embedding_dimension=self._get_embedding_dimension(), + vector_dimension=self._get_embedding_dimension(), + metadata_indexing="all", primary_key_type="TEXT", ) @@ -127,7 +128,7 @@ def clear(self) -> None: self.table.clear() def delete_by_document_id(self, document_id: str) -> None: - return self.table.delete(document_id) + return self.table.delete(row_id=document_id) def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> Optional[bool]: """Delete by vector IDs. @@ -188,7 +189,11 @@ def add_texts( futures = [ self.table.put_async( - text, embedding_vector, text_id, metadata, ttl_seconds + row_id=text_id, + body_blob=text, + vector=embedding_vector, + metadata=metadata or {}, + ttl_seconds=ttl_seconds, ) for text, embedding_vector, text_id, metadata in zip( batch_texts, batch_embedding_vectors, batch_ids, batch_metadatas @@ -215,11 +220,10 @@ def similarity_search_with_score_id_by_vector( """ search_metadata = self._filter_to_metadata(filter) # - hits = self.table.search( - embedding_vector=embedding, - top_k=k, + hits = self.table.metric_ann_search( + vector=embedding, + n=k, metric="cos", - metric_threshold=None, metadata=search_metadata, ) # We stick to 'cos' distance as it can be normalized on a 0-1 axis @@ -227,11 +231,11 @@ def similarity_search_with_score_id_by_vector( return [ ( Document( - page_content=hit["document"], + page_content=hit["body_blob"], metadata=hit["metadata"], ), 0.5 + 0.5 * hit["distance"], - hit["document_id"], + hit["row_id"], ) for hit in hits ] @@ -340,31 +344,32 @@ def max_marginal_relevance_search_by_vector( """ search_metadata = self._filter_to_metadata(filter) - prefetchHits = self.table.search( - embedding_vector=embedding, - top_k=fetch_k, - metric="cos", - metric_threshold=None, - metadata=search_metadata, + prefetch_hits = list( + self.table.metric_ann_search( + vector=embedding, + n=fetch_k, + metric="cos", + metadata=search_metadata, + ) ) # let the mmr utility pick the *indices* in the above array - mmrChosenIndices = maximal_marginal_relevance( + mmr_chosen_indices = maximal_marginal_relevance( np.array(embedding, dtype=np.float32), - [pfHit["embedding_vector"] for pfHit in prefetchHits], + [pf_hit["vector"] for pf_hit in prefetch_hits], k=k, lambda_mult=lambda_mult, ) - mmrHits = [ - pfHit - for pfIndex, pfHit in enumerate(prefetchHits) - if pfIndex in mmrChosenIndices + mmr_hits = [ + pf_hit + for pf_index, pf_hit in enumerate(prefetch_hits) + if pf_index in mmr_chosen_indices ] return [ Document( - page_content=hit["document"], + page_content=hit["body_blob"], metadata=hit["metadata"], ) - for hit in mmrHits + for hit in mmr_hits ] def max_marginal_relevance_search( From 5c73fd5bbae4fd79cdfbee6b9276e163f8aa9d2d Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Wed, 17 Jan 2024 11:26:25 -0800 Subject: [PATCH 070/215] core[patch]: support old core namespaces (#16155) --- libs/core/langchain_core/load/load.py | 4 +- libs/core/langchain_core/load/mapping.py | 337 ++++++++++++++++++----- libs/core/pyproject.toml | 2 +- 3 files changed, 270 insertions(+), 73 deletions(-) diff --git a/libs/core/langchain_core/load/load.py b/libs/core/langchain_core/load/load.py index dbf0f2c77d94e..f10e1cb680227 100644 --- a/libs/core/langchain_core/load/load.py +++ b/libs/core/langchain_core/load/load.py @@ -6,7 +6,7 @@ from langchain_core._api import beta from langchain_core.load.mapping import ( _OG_SERIALIZABLE_MAPPING, - OLD_PROMPT_TEMPLATE_FORMATS, + OLD_CORE_NAMESPACES_MAPPING, SERIALIZABLE_MAPPING, ) from langchain_core.load.serializable import Serializable @@ -15,7 +15,7 @@ ALL_SERIALIZABLE_MAPPINGS = { **SERIALIZABLE_MAPPING, - **OLD_PROMPT_TEMPLATE_FORMATS, + **OLD_CORE_NAMESPACES_MAPPING, **_OG_SERIALIZABLE_MAPPING, } diff --git a/libs/core/langchain_core/load/mapping.py b/libs/core/langchain_core/load/mapping.py index 755428c363ffb..7ac3c7d3e3858 100644 --- a/libs/core/langchain_core/load/mapping.py +++ b/libs/core/langchain_core/load/mapping.py @@ -513,102 +513,239 @@ } # Needed for backwards compatibility for a few versions where we serialized -# with langchain_core -OLD_PROMPT_TEMPLATE_FORMATS: Dict[Tuple[str, ...], Tuple[str, ...]] = { - ( +# with langchain_core paths. +OLD_CORE_NAMESPACES_MAPPING: Dict[Tuple[str, ...], Tuple[str, ...]] = { + ("langchain_core", "messages", "ai", "AIMessage"): ( "langchain_core", - "prompts", + "messages", + "ai", + "AIMessage", + ), + ("langchain_core", "messages", "ai", "AIMessageChunk"): ( + "langchain_core", + "messages", + "ai", + "AIMessageChunk", + ), + ("langchain_core", "messages", "base", "BaseMessage"): ( + "langchain_core", + "messages", "base", - "BasePromptTemplate", - ): ( + "BaseMessage", + ), + ("langchain_core", "messages", "base", "BaseMessageChunk"): ( "langchain_core", - "prompts", + "messages", "base", - "BasePromptTemplate", + "BaseMessageChunk", ), - ( + ("langchain_core", "messages", "chat", "ChatMessage"): ( + "langchain_core", + "messages", + "chat", + "ChatMessage", + ), + ("langchain_core", "messages", "function", "FunctionMessage"): ( + "langchain_core", + "messages", + "function", + "FunctionMessage", + ), + ("langchain_core", "messages", "human", "HumanMessage"): ( + "langchain_core", + "messages", + "human", + "HumanMessage", + ), + ("langchain_core", "messages", "system", "SystemMessage"): ( + "langchain_core", + "messages", + "system", + "SystemMessage", + ), + ("langchain_core", "messages", "tool", "ToolMessage"): ( + "langchain_core", + "messages", + "tool", + "ToolMessage", + ), + ("langchain_core", "agents", "AgentAction"): ( + "langchain_core", + "agents", + "AgentAction", + ), + ("langchain_core", "agents", "AgentFinish"): ( + "langchain_core", + "agents", + "AgentFinish", + ), + ("langchain_core", "prompts", "base", "BasePromptTemplate"): ( "langchain_core", "prompts", - "prompt", - "PromptTemplate", - ): ( + "base", + "BasePromptTemplate", + ), + ("langchain_core", "prompts", "prompt", "PromptTemplate"): ( "langchain_core", "prompts", "prompt", "PromptTemplate", ), - ( - "langchain_core", - "prompts", - "chat", - "MessagesPlaceholder", - ): ( + ("langchain_core", "prompts", "chat", "MessagesPlaceholder"): ( "langchain_core", "prompts", "chat", "MessagesPlaceholder", ), - ( - "langchain_core", - "prompts", - "chat", - "ChatPromptTemplate", - ): ( + ("langchain_core", "prompts", "chat", "ChatPromptTemplate"): ( "langchain_core", "prompts", "chat", "ChatPromptTemplate", ), - ( - "langchain_core", - "prompts", - "chat", - "HumanMessagePromptTemplate", - ): ( + ("langchain_core", "prompts", "chat", "HumanMessagePromptTemplate"): ( "langchain_core", "prompts", "chat", "HumanMessagePromptTemplate", ), - ( + ("langchain_core", "prompts", "chat", "SystemMessagePromptTemplate"): ( "langchain_core", "prompts", "chat", "SystemMessagePromptTemplate", - ): ( + ), + ("langchain_core", "agents", "AgentActionMessageLog"): ( "langchain_core", - "prompts", - "chat", - "SystemMessagePromptTemplate", + "agents", + "AgentActionMessageLog", ), - ( + ("langchain_core", "prompts", "chat", "BaseMessagePromptTemplate"): ( "langchain_core", "prompts", "chat", "BaseMessagePromptTemplate", - ): ( + ), + ("langchain_core", "outputs", "chat_generation", "ChatGeneration"): ( "langchain_core", - "prompts", - "chat", - "BaseMessagePromptTemplate", + "outputs", + "chat_generation", + "ChatGeneration", ), - ( + ("langchain_core", "outputs", "generation", "Generation"): ( + "langchain_core", + "outputs", + "generation", + "Generation", + ), + ("langchain_core", "documents", "base", "Document"): ( + "langchain_core", + "documents", + "base", + "Document", + ), + ("langchain_core", "prompts", "chat", "AIMessagePromptTemplate"): ( "langchain_core", "prompts", "chat", - "BaseChatPromptTemplate", - ): ( + "AIMessagePromptTemplate", + ), + ("langchain_core", "runnables", "configurable", "DynamicRunnable"): ( "langchain_core", - "prompts", + "runnables", + "configurable", + "DynamicRunnable", + ), + ("langchain_core", "prompt_values", "PromptValue"): ( + "langchain_core", + "prompt_values", + "PromptValue", + ), + ("langchain_core", "runnables", "base", "RunnableBinding"): ( + "langchain_core", + "runnables", + "base", + "RunnableBinding", + ), + ("langchain_core", "runnables", "branch", "RunnableBranch"): ( + "langchain_core", + "runnables", + "branch", + "RunnableBranch", + ), + ("langchain_core", "runnables", "fallbacks", "RunnableWithFallbacks"): ( + "langchain_core", + "runnables", + "fallbacks", + "RunnableWithFallbacks", + ), + ("langchain_core", "output_parsers", "string", "StrOutputParser"): ( + "langchain_core", + "output_parsers", + "string", + "StrOutputParser", + ), + ("langchain_core", "output_parsers", "list", "CommaSeparatedListOutputParser"): ( + "langchain_core", + "output_parsers", + "list", + "CommaSeparatedListOutputParser", + ), + ("langchain_core", "runnables", "base", "RunnableParallel"): ( + "langchain_core", + "runnables", + "base", + "RunnableParallel", + ), + ("langchain_core", "outputs", "chat_generation", "ChatGenerationChunk"): ( + "langchain_core", + "outputs", + "chat_generation", + "ChatGenerationChunk", + ), + ("langchain_core", "messages", "chat", "ChatMessageChunk"): ( + "langchain_core", + "messages", "chat", - "BaseChatPromptTemplate", + "ChatMessageChunk", ), - ( + ("langchain_core", "messages", "human", "HumanMessageChunk"): ( + "langchain_core", + "messages", + "human", + "HumanMessageChunk", + ), + ("langchain_core", "messages", "function", "FunctionMessageChunk"): ( + "langchain_core", + "messages", + "function", + "FunctionMessageChunk", + ), + ("langchain_core", "messages", "system", "SystemMessageChunk"): ( + "langchain_core", + "messages", + "system", + "SystemMessageChunk", + ), + ("langchain_core", "messages", "tool", "ToolMessageChunk"): ( + "langchain_core", + "messages", + "tool", + "ToolMessageChunk", + ), + ("langchain_core", "outputs", "generation", "GenerationChunk"): ( + "langchain_core", + "outputs", + "generation", + "GenerationChunk", + ), + ("langchain_core", "prompts", "chat", "BaseChatPromptTemplate"): ( "langchain_core", "prompts", "chat", - "ChatMessagePromptTemplate", - ): ( + "BaseChatPromptTemplate", + ), + ("langchain_core", "prompts", "chat", "ChatMessagePromptTemplate"): ( "langchain_core", "prompts", "chat", @@ -625,48 +762,108 @@ "few_shot_with_templates", "FewShotPromptWithTemplates", ), - ( - "langchain_core", - "prompts", - "pipeline", - "PipelinePromptTemplate", - ): ( + ("langchain_core", "prompts", "pipeline", "PipelinePromptTemplate"): ( "langchain_core", "prompts", "pipeline", "PipelinePromptTemplate", ), - ( + ("langchain_core", "prompts", "string", "StringPromptTemplate"): ( "langchain_core", "prompts", "string", "StringPromptTemplate", - ): ( + ), + ("langchain_core", "prompt_values", "StringPromptValue"): ( "langchain_core", - "prompts", - "string", - "StringPromptTemplate", + "prompt_values", + "StringPromptValue", ), - ( + ("langchain_core", "prompts", "chat", "BaseStringMessagePromptTemplate"): ( "langchain_core", "prompts", "chat", "BaseStringMessagePromptTemplate", - ): ( + ), + ("langchain_core", "prompt_values", "ChatPromptValue"): ( "langchain_core", - "prompts", - "chat", - "BaseStringMessagePromptTemplate", + "prompt_values", + "ChatPromptValue", + ), + ("langchain_core", "prompt_values", "ChatPromptValueConcrete"): ( + "langchain_core", + "prompt_values", + "ChatPromptValueConcrete", + ), + ("langchain_core", "runnables", "base", "RunnableBindingBase"): ( + "langchain_core", + "runnables", + "base", + "RunnableBindingBase", + ), + ("langchain_core", "runnables", "router", "RouterRunnable"): ( + "langchain_core", + "runnables", + "router", + "RouterRunnable", + ), + ("langchain_core", "runnables", "passthrough", "RunnablePassthrough"): ( + "langchain_core", + "runnables", + "passthrough", + "RunnablePassthrough", + ), + ("langchain_core", "runnables", "base", "RunnableSequence"): ( + "langchain_core", + "runnables", + "base", + "RunnableSequence", + ), + ("langchain_core", "runnables", "base", "RunnableEach"): ( + "langchain_core", + "runnables", + "base", + "RunnableEach", + ), + ("langchain_core", "runnables", "base", "RunnableEachBase"): ( + "langchain_core", + "runnables", + "base", + "RunnableEachBase", ), ( "langchain_core", - "prompts", - "chat", - "AIMessagePromptTemplate", + "runnables", + "configurable", + "RunnableConfigurableAlternatives", ): ( "langchain_core", - "prompts", - "chat", - "AIMessagePromptTemplate", + "runnables", + "configurable", + "RunnableConfigurableAlternatives", + ), + ("langchain_core", "runnables", "configurable", "RunnableConfigurableFields"): ( + "langchain_core", + "runnables", + "configurable", + "RunnableConfigurableFields", + ), + ("langchain_core", "runnables", "history", "RunnableWithMessageHistory"): ( + "langchain_core", + "runnables", + "history", + "RunnableWithMessageHistory", + ), + ("langchain_core", "runnables", "passthrough", "RunnableAssign"): ( + "langchain_core", + "runnables", + "passthrough", + "RunnableAssign", + ), + ("langchain_core", "runnables", "retry", "RunnableRetry"): ( + "langchain_core", + "runnables", + "retry", + "RunnableRetry", ), } diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index caed8502ee1bc..72b7db2bf7584 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-core" -version = "0.1.11" +version = "0.1.12-rc.1" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From ec9642d667d1c6cf15b8c5cb38173869543093ab Mon Sep 17 00:00:00 2001 From: David DeCaprio <daved@alum.mit.edu> Date: Wed, 17 Jan 2024 14:07:17 -0600 Subject: [PATCH 071/215] docs: Updated MongoDB Chat history example notebook to use LCEL format. (#15750) - **Description:** Updated the MongoDB example integration notebook to latest standards - **Issue:** [15664](https://github.com/langchain-ai/langchain/issues/15664) - **Dependencies:** None - **Twitter handle:** @davedecaprio --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- .../memory/mongodb_chat_message_history.ipynb | 202 +++++++++++++++--- 1 file changed, 178 insertions(+), 24 deletions(-) diff --git a/docs/docs/integrations/memory/mongodb_chat_message_history.ipynb b/docs/docs/integrations/memory/mongodb_chat_message_history.ipynb index 4e01086b7a9ab..8356154fcda4c 100644 --- a/docs/docs/integrations/memory/mongodb_chat_message_history.ipynb +++ b/docs/docs/integrations/memory/mongodb_chat_message_history.ipynb @@ -11,7 +11,7 @@ ">\n", ">`MongoDB` is developed by MongoDB Inc. and licensed under the Server Side Public License (SSPL). - [Wikipedia](https://en.wikipedia.org/wiki/MongoDB)\n", "\n", - "This notebook goes over how to use Mongodb to store chat message history.\n" + "This notebook goes over how to use the `MongoDBChatMessageHistory` class to store chat message history in a Mongodb database.\n" ] }, { @@ -19,76 +19,230 @@ "id": "2d6ed3c8-b70a-498c-bc9e-41b91797d3b7", "metadata": {}, "source": [ - "## Setting up" + "## Setup\n", + "\n", + "The integration lives in the `langchain-community` package, so we need to install that. We also need to install the `pymongo` package.\n", + "\n", + "```bash\n", + "pip install -U --quiet langchain-community pymongo\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "09c33ad3-9ab1-48b5-bead-9a44f3d86eeb", + "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": "5a7f3b3f-d9b8-4577-a7ef-bdd8ecaedb70", + "id": "0976204d-c681-4288-bfe5-a550e0340f35", "metadata": {}, "outputs": [], "source": [ - "%pip install --upgrade --quiet pymongo" + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "71a0a5aa-8f12-462a-bcd0-c611d76566f8", + "metadata": {}, + "source": [ + "## Usage\n", + "\n", + "To use the storage you need to provide only 2 things:\n", + "\n", + "1. Session Id - a unique identifier of the session, like user name, email, chat id etc.\n", + "2. Connection string - a string that specifies the database connection. It will be passed to MongoDB create_engine function.\n", + "\n", + "If you want to customize where the chat histories go, you can also pass:\n", + "1. *database_name* - name of the database to use\n", + "1. *collection_name* - collection to use within that database" ] }, { "cell_type": "code", "execution_count": 3, - "id": "47a601d2", - "metadata": {}, + "id": "0179847d-76b6-43bc-b15c-7fecfcb27ac7", + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-28T10:04:38.077748Z", + "start_time": "2023-08-28T10:04:36.105894Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ - "# Provide the connection string to connect to the MongoDB database\n", - "connection_string = \"mongodb://mongo_user:password123@mongo:27017\"" + "from langchain_community.chat_message_histories import MongoDBChatMessageHistory\n", + "\n", + "chat_message_history = MongoDBChatMessageHistory(\n", + " session_id=\"test_session\",\n", + " connection_string=\"mongodb://mongo_user:password123@mongo:27017\",\n", + " database_name=\"my_db\",\n", + " collection_name=\"chat_histories\",\n", + ")\n", + "\n", + "chat_message_history.add_user_message(\"Hello\")\n", + "chat_message_history.add_ai_message(\"Hi\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6e7b8653-a8d2-49a7-97ba-4296f7e717e9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[HumanMessage(content='Hello'), AIMessage(content='Hi')]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chat_message_history.messages" ] }, { "cell_type": "markdown", - "id": "a8e63850-3e14-46fe-a59e-be6d6bf8fe61", + "id": "e352d786-0811-48ec-832a-9f1c0b70690e", "metadata": {}, "source": [ - "## Example" + "## Chaining\n", + "\n", + "We can easily combine this message history class with [LCEL Runnables](/docs/expression_language/how_to/message_history)\n", + "\n", + "To do this we will want to use OpenAI, so we need to install that. You will also need to set the OPENAI_API_KEY environment variable to your OpenAI key.\n" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "d15e3302", + "execution_count": 5, + "id": "6558418b-0ece-4d01-9661-56d562d78f7a", "metadata": {}, "outputs": [], "source": [ - "from langchain.memory import MongoDBChatMessageHistory\n", + "from typing import Optional\n", "\n", - "message_history = MongoDBChatMessageHistory(\n", - " connection_string=connection_string, session_id=\"test-session\"\n", - ")\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_openai import ChatOpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "86ddfd3f-e8cf-477a-a7fd-91be3b8aa928", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", "\n", - "message_history.add_user_message(\"hi!\")\n", + "assert os.environ[\n", + " \"OPENAI_API_KEY\"\n", + "], \"Set the OPENAI_API_KEY environment variable with your OpenAI API key.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "82149122-61d3-490d-9bdb-bb98606e8ba1", + "metadata": {}, + "outputs": [], + "source": [ + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", \"You are a helpful assistant.\"),\n", + " MessagesPlaceholder(variable_name=\"history\"),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", "\n", - "message_history.add_ai_message(\"whats up?\")" + "chain = prompt | ChatOpenAI()" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "64fc465e", + "execution_count": 11, + "id": "2df90853-b67c-490f-b7f8-b69d69270b9c", + "metadata": {}, + "outputs": [], + "source": [ + "chain_with_history = RunnableWithMessageHistory(\n", + " chain,\n", + " lambda session_id: MongoDBChatMessageHistory(\n", + " session_id=\"test_session\",\n", + " connection_string=\"mongodb://mongo_user:password123@mongo:27017\",\n", + " database_name=\"my_db\",\n", + " collection_name=\"chat_histories\",\n", + " ),\n", + " input_messages_key=\"question\",\n", + " history_messages_key=\"history\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0ce596b8-3b78-48fd-9f92-46dccbbfd58b", + "metadata": {}, + "outputs": [], + "source": [ + "# This is where we configure the session id\n", + "config = {\"configurable\": {\"session_id\": \"<SESSION_ID>\"}}" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "38e1423b-ba86-4496-9151-25932fab1a8b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='Hi Bob! How can I assist you today?')" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain_with_history.invoke({\"question\": \"Hi! I'm bob\"}, config=config)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2ee4ee62-a216-4fb1-bf33-57476a84cf16", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[HumanMessage(content='hi!', additional_kwargs={}, example=False),\n", - " AIMessage(content='whats up?', additional_kwargs={}, example=False)]" + "AIMessage(content='Your name is Bob. Is there anything else I can help you with, Bob?')" ] }, - "execution_count": 5, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "message_history.messages" + "chain_with_history.invoke({\"question\": \"Whats my name\"}, config=config)" ] } ], From 58f0ba306b26a28044a108035a900b39c0d97bc7 Mon Sep 17 00:00:00 2001 From: Leonid Kuligin <lkuligin@yandex.ru> Date: Wed, 17 Jan 2024 21:19:18 +0100 Subject: [PATCH 072/215] changed default params for gemini (#16044) Replace this entire comment with: - **Description:** changed default values for Vertex LLMs (to be handled on the SDK's side) --- .../langchain_google_vertexai/llms.py | 33 +++++++++--- .../tests/unit_tests/test_chat_models.py | 51 ++++++++++++++++++- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py index c3ff12d906432..6e02ae43f981c 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py @@ -41,6 +41,11 @@ is_gemini_model, ) +_PALM_DEFAULT_MAX_OUTPUT_TOKENS = TextGenerationModel._DEFAULT_MAX_OUTPUT_TOKENS +_PALM_DEFAULT_TEMPERATURE = 0.0 +_PALM_DEFAULT_TOP_P = 0.95 +_PALM_DEFAULT_TOP_K = 40 + def _completion_with_retry( llm: VertexAI, @@ -118,14 +123,14 @@ class _VertexAICommon(_VertexAIBase): client_preview: Any = None #: :meta private: model_name: str "Underlying model name." - temperature: float = 0.0 + temperature: Optional[float] = None "Sampling temperature, it controls the degree of randomness in token selection." - max_output_tokens: int = 128 + max_output_tokens: Optional[int] = None "Token limit determines the maximum amount of text output from one prompt." - top_p: float = 0.95 + top_p: Optional[float] = None "Tokens are selected from most probable to least until the sum of their " "probabilities equals the top-p value. Top-p is ignored for Codey models." - top_k: int = 40 + top_k: Optional[int] = None "How the model selects tokens for output, the next token is selected from " "among the top-k most probable tokens. Top-k is ignored for Codey models." credentials: Any = Field(default=None, exclude=True) @@ -156,6 +161,15 @@ def _identifying_params(self) -> Dict[str, Any]: @property def _default_params(self) -> Dict[str, Any]: + if self._is_gemini_model: + default_params = {} + else: + default_params = { + "temperature": _PALM_DEFAULT_TEMPERATURE, + "max_output_tokens": _PALM_DEFAULT_MAX_OUTPUT_TOKENS, + "top_p": _PALM_DEFAULT_TOP_P, + "top_k": _PALM_DEFAULT_TOP_K, + } params = { "temperature": self.temperature, "max_output_tokens": self.max_output_tokens, @@ -168,7 +182,14 @@ def _default_params(self) -> Dict[str, Any]: "top_p": self.top_p, } ) - return params + updated_params = {} + for param_name, param_value in params.items(): + default_value = default_params.get(param_name) + if param_value or default_value: + updated_params[param_name] = ( + param_value if param_value else default_value + ) + return updated_params @classmethod def _init_vertexai(cls, values: Dict) -> None: @@ -314,7 +335,7 @@ async def _agenerate( **kwargs: Any, ) -> LLMResult: params = self._prepare_params(stop=stop, **kwargs) - generations = [] + generations: List[List[Generation]] = [] for prompt in prompts: res = await _acompletion_with_retry( self, diff --git a/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py b/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py index bff39ee431875..d11a970d65423 100644 --- a/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py +++ b/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py @@ -68,7 +68,7 @@ def test_vertexai_args_passed(stop: Optional[str]) -> None: mock_model.start_chat = mock_start_chat mg.return_value = mock_model - model = ChatVertexAI(**prompt_params) + model = ChatVertexAI(**prompt_params) # type: ignore message = HumanMessage(content=user_prompt) if stop: response = model([message], stop=[stop]) @@ -110,3 +110,52 @@ def test_parse_chat_history_correct() -> None: ChatMessage(content=text_question, author="user"), ChatMessage(content=text_answer, author="bot"), ] + + +def test_default_params_palm() -> None: + user_prompt = "Hello" + + with patch("vertexai._model_garden._model_garden_models._from_pretrained") as mg: + mock_response = MagicMock() + mock_response.candidates = [Mock(text="Goodbye")] + mock_chat = MagicMock() + mock_send_message = MagicMock(return_value=mock_response) + mock_chat.send_message = mock_send_message + + mock_model = MagicMock() + mock_start_chat = MagicMock(return_value=mock_chat) + mock_model.start_chat = mock_start_chat + mg.return_value = mock_model + + model = ChatVertexAI(model_name="text-bison@001") + message = HumanMessage(content=user_prompt) + _ = model([message]) + mock_start_chat.assert_called_once_with( + context=None, + message_history=[], + max_output_tokens=128, + top_k=40, + top_p=0.95, + stop_sequences=None, + ) + + +def test_default_params_gemini() -> None: + user_prompt = "Hello" + + with patch("langchain_google_vertexai.chat_models.GenerativeModel") as gm: + mock_response = MagicMock() + content = Mock(parts=[Mock(function_call=None)]) + mock_response.candidates = [Mock(text="Goodbye", content=content)] + mock_chat = MagicMock() + mock_send_message = MagicMock(return_value=mock_response) + mock_chat.send_message = mock_send_message + + mock_model = MagicMock() + mock_start_chat = MagicMock(return_value=mock_chat) + mock_model.start_chat = mock_start_chat + gm.return_value = mock_model + model = ChatVertexAI(model_name="gemini-pro") + message = HumanMessage(content=user_prompt) + _ = model([message]) + mock_start_chat.assert_called_once_with(history=[]) From 7ad9eba8f4b665caa823347c7a4d7a906f71eb72 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:39:45 -0800 Subject: [PATCH 073/215] core[patch]: Release 0.1.12 (#16161) --- libs/core/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 72b7db2bf7584..1770fb6478e3d 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-core" -version = "0.1.12-rc.1" +version = "0.1.12" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From 679a3ae933ef1334500df1bb8c83b225ea0d1436 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:43:14 -0800 Subject: [PATCH 074/215] openai[patch]: clarify azure error (#16157) --- .../openai/langchain_openai/chat_models/azure.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/libs/partners/openai/langchain_openai/chat_models/azure.py b/libs/partners/openai/langchain_openai/chat_models/azure.py index 8c57825c6ffdc..149a5a66c91ed 100644 --- a/libs/partners/openai/langchain_openai/chat_models/azure.py +++ b/libs/partners/openai/langchain_openai/chat_models/azure.py @@ -151,11 +151,16 @@ def validate_environment(cls, values: Dict) -> Dict: ) if values["deployment_name"]: raise ValueError( - "As of openai>=1.0.0, if `deployment_name` (or alias " - "`azure_deployment`) is specified then " - "`openai_api_base` (or alias `base_url`) should not be. " - "Instead use `deployment_name` (or alias `azure_deployment`) " - "and `azure_endpoint`." + "As of openai>=1.0.0, if `azure_deployment` (or alias " + "`deployment_name`) is specified then " + "`base_url` (or alias `openai_api_base`) should not be. " + "If specifying `azure_deployment`/`deployment_name` then use " + "`azure_endpoint` instead of `base_url`.\n\n" + "For example, you could specify:\n\n" + 'azure_deployment="https://xxx.openai.azure.com/", ' + 'deployment_name="my-deployment"\n\n' + "Or you can equivalently specify:\n\n" + 'base_url="https://xxx.openai.azure.com/openai/deployments/my-deployment"' # noqa: E501 ) client_params = { "api_version": values["openai_api_version"], From 2af813c7eb42b34f93c932ef37e49a2e0e082ec4 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:57:34 -0800 Subject: [PATCH 075/215] docs: bump sphinx>=5 (#16162) --- docs/api_reference/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api_reference/requirements.txt b/docs/api_reference/requirements.txt index e5829ed5d9cdc..9132e119aef1e 100644 --- a/docs/api_reference/requirements.txt +++ b/docs/api_reference/requirements.txt @@ -6,7 +6,7 @@ pydantic<2 autodoc_pydantic==1.8.0 myst_parser nbsphinx==0.8.9 -sphinx==4.5.0 +sphinx>=5 sphinx-autobuild==2021.3.14 sphinx_rtd_theme==1.0.0 sphinx-typlog-theme==0.8.0 From f238217cea34a6fbb503c3baca9848b8862e0afc Mon Sep 17 00:00:00 2001 From: Krishna Shedbalkar <60742358+krishnashed@users.noreply.github.com> Date: Thu, 18 Jan 2024 02:27:51 +0530 Subject: [PATCH 076/215] community[patch]: Basic Logging and Human input to ShellTool (#15932) - **Description:** As Shell tool is very versatile, while integrating it into applications as openai functions, developers have no clue about what command is being executed using the ShellTool. All one can see is: ![image](https://github.com/langchain-ai/langchain/assets/60742358/540e274a-debc-4564-9027-046b91424df3) Summarising my feature request: 1. There's no visibility about what command was executed. 2. There's no mechanism to prevent a command to be executed using ShellTool, like a y/n human input which can be accepted from user to proceed with executing the command., - **Issue:** the issue #15931 it fixes if applicable, - **Dependencies:** There isn't any dependancy, - **Twitter handle:** @krishnashed --- .../langchain_community/tools/shell/tool.py | 27 ++++++++++++++++++- .../unit_tests/tools/shell/test_shell.py | 27 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/tools/shell/tool.py b/libs/community/langchain_community/tools/shell/tool.py index 5f61631059d92..e26deb365dcfc 100644 --- a/libs/community/langchain_community/tools/shell/tool.py +++ b/libs/community/langchain_community/tools/shell/tool.py @@ -1,3 +1,4 @@ +import logging import platform import warnings from typing import Any, List, Optional, Type, Union @@ -8,6 +9,8 @@ from langchain_core.pydantic_v1 import BaseModel, Field, root_validator from langchain_core.tools import BaseTool +logger = logging.getLogger(__name__) + class ShellInput(BaseModel): """Commands for the Bash Shell tool.""" @@ -68,10 +71,32 @@ class ShellTool(BaseTool): args_schema: Type[BaseModel] = ShellInput """Schema for input arguments.""" + ask_human_input: bool = False + """ + If True, prompts the user for confirmation (y/n) before executing + a command generated by the language model in the bash shell. + """ + def _run( self, commands: Union[str, List[str]], run_manager: Optional[CallbackManagerForToolRun] = None, ) -> str: """Run commands and return final output.""" - return self.process.run(commands) + + print(f"Executing command:\n {commands}") + + try: + if self.ask_human_input: + user_input = input("Proceed with command execution? (y/n): ").lower() + if user_input == "y": + return self.process.run(commands) + else: + logger.info("Invalid input. User aborted command execution.") + return None + else: + return self.process.run(commands) + + except Exception as e: + logger.error(f"Error during command execution: {e}") + return None diff --git a/libs/community/tests/unit_tests/tools/shell/test_shell.py b/libs/community/tests/unit_tests/tools/shell/test_shell.py index ab6b5abe38c8e..b792505f1c435 100644 --- a/libs/community/tests/unit_tests/tools/shell/test_shell.py +++ b/libs/community/tests/unit_tests/tools/shell/test_shell.py @@ -1,5 +1,6 @@ import warnings from typing import List +from unittest.mock import patch from langchain_community.tools.shell.tool import ShellInput, ShellTool @@ -65,3 +66,29 @@ def test_shell_tool_run_str() -> None: shell_tool = ShellTool(process=placeholder) result = shell_tool._run(commands="echo 'Hello, World!'") assert result.strip() == "hello" + + +async def test_shell_tool_arun_with_user_confirmation() -> None: + placeholder = PlaceholderProcess(output="hello") + shell_tool = ShellTool(process=placeholder, ask_human_input=True) + + with patch("builtins.input", return_value="y"): + result = await shell_tool._arun(commands=test_commands) + assert result.strip() == "hello" + + with patch("builtins.input", return_value="n"): + result = await shell_tool._arun(commands=test_commands) + assert result is None + + +def test_shell_tool_run_with_user_confirmation() -> None: + placeholder = PlaceholderProcess(output="hello") + shell_tool = ShellTool(process=placeholder, ask_human_input=True) + + with patch("builtins.input", return_value="y"): + result = shell_tool._run(commands="echo 'Hello, World!'") + assert result.strip() == "hello" + + with patch("builtins.input", return_value="n"): + result = shell_tool._run(commands="echo 'Hello, World!'") + assert result is None From 27ed2673da50c1d7d796cf3a26b0eebee18c7e31 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:13:31 -0800 Subject: [PATCH 077/215] docs: model io order (#16163) --- docs/docs/modules/model_io/chat/index.mdx | 2 +- docs/docs/modules/model_io/concepts.mdx | 2 +- docs/docs/modules/model_io/llms/index.mdx | 2 +- docs/docs/modules/model_io/output_parsers/index.mdx | 2 +- docs/docs/modules/model_io/prompts/index.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/modules/model_io/chat/index.mdx b/docs/docs/modules/model_io/chat/index.mdx index 99e72f841d5c2..9c4de13b27e49 100644 --- a/docs/docs/modules/model_io/chat/index.mdx +++ b/docs/docs/modules/model_io/chat/index.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Chat Models diff --git a/docs/docs/modules/model_io/concepts.mdx b/docs/docs/modules/model_io/concepts.mdx index 2e688d8d12df8..b9542076df1cc 100644 --- a/docs/docs/modules/model_io/concepts.mdx +++ b/docs/docs/modules/model_io/concepts.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 0 +sidebar_position: 1 --- # Concepts diff --git a/docs/docs/modules/model_io/llms/index.mdx b/docs/docs/modules/model_io/llms/index.mdx index 396e7315f02d0..8e7a1e95dabb4 100644 --- a/docs/docs/modules/model_io/llms/index.mdx +++ b/docs/docs/modules/model_io/llms/index.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 4 --- # LLMs diff --git a/docs/docs/modules/model_io/output_parsers/index.mdx b/docs/docs/modules/model_io/output_parsers/index.mdx index ac479f6918418..2f30437618344 100644 --- a/docs/docs/modules/model_io/output_parsers/index.mdx +++ b/docs/docs/modules/model_io/output_parsers/index.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 hide_table_of_contents: true --- # Output Parsers diff --git a/docs/docs/modules/model_io/prompts/index.mdx b/docs/docs/modules/model_io/prompts/index.mdx index 53ae4f77546b1..52a5a34bd947a 100644 --- a/docs/docs/modules/model_io/prompts/index.mdx +++ b/docs/docs/modules/model_io/prompts/index.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 0 +sidebar_position: 2 --- # Prompts From 1e80113ac98b286e581c0210d3bbdcdc57f4e87c Mon Sep 17 00:00:00 2001 From: Tomaz Bratanic <bratanic.tomaz@gmail.com> Date: Wed, 17 Jan 2024 22:22:19 +0100 Subject: [PATCH 078/215] community[patch]: Add neo4j timeout and value sanitization option (#16138) The timeout function comes in handy when you want to kill longrunning queries. The value sanitization removes all lists that are larger than 128 elements. The idea here is to remove embedding properties from results. --- .../langchain_community/graphs/neo4j_graph.py | 56 ++++++++++++++++++- .../integration_tests/graphs/test_neo4j.py | 19 +++++++ .../unit_tests/graphs/test_neo4j_graph.py | 32 +++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 libs/community/tests/unit_tests/graphs/test_neo4j_graph.py diff --git a/libs/community/langchain_community/graphs/neo4j_graph.py b/libs/community/langchain_community/graphs/neo4j_graph.py index af416b29bc541..92efd27f0813c 100644 --- a/libs/community/langchain_community/graphs/neo4j_graph.py +++ b/libs/community/langchain_community/graphs/neo4j_graph.py @@ -31,8 +31,50 @@ """ +def value_sanitize(d: Dict[str, Any]) -> Dict[str, Any]: + """ + Sanitizes the input dictionary by removing embedding-like values, + lists with more than 128 elements, that are mostly irrelevant for + generating answers in a LLM context. These properties, if left in + results, can occupy significant context space and detract from + the LLM's performance by introducing unnecessary noise and cost. + """ + LIST_LIMIT = 128 + # Create a new dictionary to avoid changing size during iteration + new_dict = {} + for key, value in d.items(): + if isinstance(value, dict): + # Recurse to handle nested dictionaries + new_dict[key] = value_sanitize(value) + elif isinstance(value, list): + # check if it has less than LIST_LIMIT values + if len(value) < LIST_LIMIT: + # if value is a list, check if it contains dictionaries to clean + cleaned_list = [] + for item in value: + if isinstance(item, dict): + cleaned_list.append(value_sanitize(item)) + else: + cleaned_list.append(item) + new_dict[key] = cleaned_list + else: + new_dict[key] = value + return new_dict + + class Neo4jGraph(GraphStore): - """Neo4j wrapper for graph operations. + """Provides a connection to a Neo4j database for various graph operations. + Parameters: + url (Optional[str]): The URL of the Neo4j database server. + username (Optional[str]): The username for database authentication. + password (Optional[str]): The password for database authentication. + database (str): The name of the database to connect to. Default is 'neo4j'. + timeout (Optional[float]): The timeout for transactions in seconds. + Useful for terminating long-running queries. + By default, there is no timeout set. + sanitize (bool): A flag to indicate whether to remove lists with + more than 128 elements from results. Useful for removing + embedding-like properties from database responses. Default is False. *Security note*: Make sure that the database connection uses credentials that are narrowly-scoped to only include necessary permissions. @@ -52,6 +94,8 @@ def __init__( username: Optional[str] = None, password: Optional[str] = None, database: str = "neo4j", + timeout: Optional[float] = None, + sanitize: bool = False, ) -> None: """Create a new Neo4j graph wrapper instance.""" try: @@ -69,6 +113,8 @@ def __init__( self._driver = neo4j.GraphDatabase.driver(url, auth=(username, password)) self._database = database + self.timeout = timeout + self.sanitize = sanitize self.schema: str = "" self.structured_schema: Dict[str, Any] = {} # Verify connection @@ -106,12 +152,16 @@ def get_structured_schema(self) -> Dict[str, Any]: def query(self, query: str, params: dict = {}) -> List[Dict[str, Any]]: """Query Neo4j database.""" + from neo4j import Query from neo4j.exceptions import CypherSyntaxError with self._driver.session(database=self._database) as session: try: - data = session.run(query, params) - return [r.data() for r in data] + data = session.run(Query(text=query, timeout=self.timeout), params) + json_data = [r.data() for r in data] + if self.sanitize: + json_data = value_sanitize(json_data) + return json_data except CypherSyntaxError as e: raise ValueError(f"Generated Cypher Statement is not valid\n{e}") diff --git a/libs/community/tests/integration_tests/graphs/test_neo4j.py b/libs/community/tests/integration_tests/graphs/test_neo4j.py index fd9e3bb36f786..948fe4c7190ac 100644 --- a/libs/community/tests/integration_tests/graphs/test_neo4j.py +++ b/libs/community/tests/integration_tests/graphs/test_neo4j.py @@ -69,3 +69,22 @@ def test_cypher_return_correct_schema() -> None: sorted(relationships, key=lambda x: x["output"]["end"]) == expected_relationships ) + + +def test_neo4j_timeout() -> None: + """Test that neo4j uses the timeout correctly.""" + url = os.environ.get("NEO4J_URI") + username = os.environ.get("NEO4J_USERNAME") + password = os.environ.get("NEO4J_PASSWORD") + assert url is not None + assert username is not None + assert password is not None + + graph = Neo4jGraph(url=url, username=username, password=password, timeout=0.1) + try: + graph.query("UNWIND range(0,100000,1) AS i MERGE (:Foo {id:i})") + except Exception as e: + assert ( + e.code + == "Neo.ClientError.Transaction.TransactionTimedOutClientConfiguration" + ) diff --git a/libs/community/tests/unit_tests/graphs/test_neo4j_graph.py b/libs/community/tests/unit_tests/graphs/test_neo4j_graph.py new file mode 100644 index 0000000000000..b352529ba6734 --- /dev/null +++ b/libs/community/tests/unit_tests/graphs/test_neo4j_graph.py @@ -0,0 +1,32 @@ +from langchain_community.graphs.neo4j_graph import value_sanitize + + +def test_value_sanitize_with_small_list(): + small_list = list(range(15)) # list size > LIST_LIMIT + input_dict = {"key1": "value1", "small_list": small_list} + expected_output = {"key1": "value1", "small_list": small_list} + assert value_sanitize(input_dict) == expected_output + + +def test_value_sanitize_with_oversized_list(): + oversized_list = list(range(150)) # list size > LIST_LIMIT + input_dict = {"key1": "value1", "oversized_list": oversized_list} + expected_output = { + "key1": "value1" + # oversized_list should not be included + } + assert value_sanitize(input_dict) == expected_output + + +def test_value_sanitize_with_nested_oversized_list(): + oversized_list = list(range(150)) # list size > LIST_LIMIT + input_dict = {"key1": "value1", "oversized_list": {"key": oversized_list}} + expected_output = {"key1": "value1", "oversized_list": {}} + assert value_sanitize(input_dict) == expected_output + + +def test_value_sanitize_with_dict_in_list(): + oversized_list = list(range(150)) # list size > LIST_LIMIT + input_dict = {"key1": "value1", "oversized_list": [1, 2, {"key": oversized_list}]} + expected_output = {"key1": "value1", "oversized_list": [1, 2, {}]} + assert value_sanitize(input_dict) == expected_output From ca014d5b04b1d73fd8f0fe224def98a82600c991 Mon Sep 17 00:00:00 2001 From: Nuno Campos <nuno@langchain.dev> Date: Wed, 17 Jan 2024 13:56:07 -0800 Subject: [PATCH 079/215] Update readme (#16160) <!-- Thank you for contributing to LangChain! Please title your PR "<package>: <description>", where <package> is whichever of langchain, community, core, experimental, etc. is being modified. Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes if applicable, - **Dependencies:** any dependencies required for this change, - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` from the root of the package you've modified to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ 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. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> --- libs/core/README.md | 56 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/libs/core/README.md b/libs/core/README.md index 019f5b400bdce..e110c443dfa87 100644 --- a/libs/core/README.md +++ b/libs/core/README.md @@ -11,29 +11,50 @@ pip install langchain-core ## What is it? -LangChain Core contains the base abstractions that power the rest of the LangChain ecosystem. -These abstractions are designed to be as modular and simple as possible. -Examples of these abstractions include those for language models, document loaders, embedding models, vectorstores, retrievers, and more. +LangChain Core contains the base abstractions that power the rest of the LangChain ecosystem. + +These abstractions are designed to be as modular and simple as possible. Examples of these abstractions include those for language models, document loaders, embedding models, vectorstores, retrievers, and more. + The benefit of having these abstractions is that any provider can implement the required interface and then easily be used in the rest of the LangChain ecosystem. For full documentation see the [API reference](https://api.python.langchain.com/en/stable/core_api_reference.html). -## What is LangChain Expression Language? +## 1️⃣ Core Interface: Runnables + +The concept of a Runnable is central to LangChain Core – it is the interface that most LangChain Core components implement, giving them + +- a common invocation interface (invoke, batch, stream, etc.) +- built-in utilities for retries, fallbacks, schemas and runtime configurability +- easy deployment with [LangServe](https://github.com/langchain-ai/langserve) + +For more check out the [runnable docs](https://python.langchain.com/docs/expression_language/interface). Examples of components that implement the interface include: LLMs, Chat Models, Prompts, Retrievers, Tools, Output Parsers. + +You can use LangChain Core objects in two ways: + +1. **imperative**, ie. call them directly, eg. `model.invoke(...)` -LangChain Core also contains LangChain Expression Language, or LCEL, a runtime that allows users to compose arbitrary sequences together and get several benefits that are important when building LLM applications. -We call these sequences “runnables”. +2. **declarative**, with LangChain Expression Language (LCEL) -All runnables expose the same interface with single-invocation, batch, streaming and async methods. -This design is useful because it is not enough to have a single sync interface when building an LLM application. -Batch is needed for efficient processing of many inputs. -Streaming (and streaming of intermediate steps) is needed to show the user that progress is being made. -Async interfaces are nice when moving into production. -Rather than having to write multiple implementations for all of those, LCEL allows you to write a runnable once and invoke it in many different ways. +| Feature | Imperative | Declarative | +| --------- | ------------------------------- | -------------- | +| Syntax | All of Python | LCEL | +| Tracing | ✅ – Automatic | ✅ – Automatic | +| Parallel | ✅ – with threads or coroutines | ✅ – Automatic | +| Streaming | ✅ – by yielding | ✅ – Automatic | +| Async | ✅ – by writing async functions | ✅ – Automatic | + +## ⚡️ What is LangChain Expression Language? + +LangChain Expression Language (LCEL) is a _declarative language_ for composing LangChain Core runnables into sequences (or DAGs), covering the most common patterns when building with LLMs. + +LangChain Core compiles LCEL sequences to an _optimized execution plan_, with automatic parallelization, streaming, tracing, and async support. For more check out the [LCEL docs](https://python.langchain.com/docs/expression_language/). ![Diagram outlining the hierarchical organization of the LangChain framework, displaying the interconnected parts across multiple layers.](../../docs/static/img/langchain_stack.png "LangChain Framework Overview") +For more advanced use cases, also check out [LangGraph](https://github.com/langchain-ai/langgraph), which is a graph-based runner for cyclic and recursive LLM workflows. + ## 📕 Releases & Versioning `langchain-core` is currently on version `0.1.x`. @@ -55,4 +76,13 @@ Patch version increases will occur for: As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation. -For detailed information on how to contribute, see the [Contributing Guide](https://python.langchain.com/docs/contributing/). \ No newline at end of file +For detailed information on how to contribute, see the [Contributing Guide](https://python.langchain.com/docs/contributing/). + +## ⛰️ Why build on top of LangChain Core? + +The whole LangChain ecosystem is built on top of LangChain Core, so you're in good company when building on top of it. Some of the benefits: + +- **Modularity**: LangChain Core is designed around abstractions that are independent of each other, and not tied to any specific model provider. +- **Stability**: We are committed to a stable versioning scheme, and will communicate any breaking changes with advance notice and version bumps. +- **Battle-tested**: LangChain Core components have the largest install base in the LLM ecosystem, and are used in production by many companies. +- **Community**: LangChain Core is developed in the open, and we welcome contributions from the community. From 3502a407d9f122162922ed918c6438598fd9a442 Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Thu, 18 Jan 2024 03:18:26 +0100 Subject: [PATCH 080/215] infra: Use dotenv in langchain-community's integration tests (#16137) * Removed some env vars not used in langchain package IT * Added Astra DB env vars in langchain package, used for cache tests * Added conftest.py to load env vars in langchain_community IT * Added .env.example in langchain_community IT --- .../tests/integration_tests/.env.example | 45 +++++++++++++++++++ .../tests/integration_tests/conftest.py | 19 ++++++++ .../tests/integration_tests/.env.example | 23 ++-------- 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 libs/community/tests/integration_tests/.env.example create mode 100644 libs/community/tests/integration_tests/conftest.py diff --git a/libs/community/tests/integration_tests/.env.example b/libs/community/tests/integration_tests/.env.example new file mode 100644 index 0000000000000..4ce3040f3434f --- /dev/null +++ b/libs/community/tests/integration_tests/.env.example @@ -0,0 +1,45 @@ +# openai +# your api key from https://platform.openai.com/account/api-keys +OPENAI_API_KEY=your_openai_api_key_here + + +# searchapi +# your api key from https://www.searchapi.io/ +SEARCHAPI_API_KEY=your_searchapi_api_key_here + + +# astra db +ASTRA_DB_API_ENDPOINT=https://your_astra_db_id-your_region.apps.astra.datastax.com +ASTRA_DB_APPLICATION_TOKEN=AstraCS:your_astra_db_application_token +# ASTRA_DB_KEYSPACE=your_astra_db_namespace + + +# pinecone +# your api key from left menu "API Keys" in https://app.pinecone.io +PINECONE_API_KEY=your_pinecone_api_key_here +# your pinecone environment from left menu "API Keys" in https://app.pinecone.io +PINECONE_ENVIRONMENT=us-west4-gcp + + +# jira +# your api token from https://id.atlassian.com/manage-profile/security/api-tokens +# more details here: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html +# JIRA_API_TOKEN=your_jira_api_token_here +# JIRA_USERNAME=your_jira_username_here +# JIRA_INSTANCE_URL=your_jira_instance_url_here + + +# clickup +CLICKUP_ACCESS_TOKEN=your_clickup_access_token + + +# power bi +# sign in to azure in order to authenticate with DefaultAzureCredentials +# details here https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet +POWERBI_DATASET_ID=_powerbi_dataset_id_here +POWERBI_TABLE_NAME=_test_table_name_here +POWERBI_NUMROWS=_num_rows_in_your_test_table + + +# MongoDB Atlas Vector Search +MONGODB_ATLAS_URI=your_mongodb_atlas_connection_string diff --git a/libs/community/tests/integration_tests/conftest.py b/libs/community/tests/integration_tests/conftest.py new file mode 100644 index 0000000000000..02b518e8695a2 --- /dev/null +++ b/libs/community/tests/integration_tests/conftest.py @@ -0,0 +1,19 @@ +# Getting the absolute path of the current file's directory +import os + +ABS_PATH = os.path.dirname(os.path.abspath(__file__)) + +# Getting the absolute path of the project's root directory +PROJECT_DIR = os.path.abspath(os.path.join(ABS_PATH, os.pardir, os.pardir)) + + +# Loading the .env file if it exists +def _load_env() -> None: + dotenv_path = os.path.join(PROJECT_DIR, "tests", "integration_tests", ".env") + if os.path.exists(dotenv_path): + from dotenv import load_dotenv + + load_dotenv(dotenv_path) + + +_load_env() diff --git a/libs/langchain/tests/integration_tests/.env.example b/libs/langchain/tests/integration_tests/.env.example index 40c520736d729..9e8ebfbdc559c 100644 --- a/libs/langchain/tests/integration_tests/.env.example +++ b/libs/langchain/tests/integration_tests/.env.example @@ -8,23 +8,6 @@ OPENAI_API_KEY=your_openai_api_key_here SEARCHAPI_API_KEY=your_searchapi_api_key_here -# pinecone -# your api key from left menu "API Keys" in https://app.pinecone.io -PINECONE_API_KEY=your_pinecone_api_key_here -# your pinecone environment from left menu "API Keys" in https://app.pinecone.io -PINECONE_ENVIRONMENT=us-west4-gcp - - -# jira -# your api token from https://id.atlassian.com/manage-profile/security/api-tokens -# more details here: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html -# JIRA_API_TOKEN=your_jira_api_token_here -# JIRA_USERNAME=your_jira_username_here -# JIRA_INSTANCE_URL=your_jira_instance_url_here - -# clickup -CLICKUP_ACCESS_TOKEN=your_clickup_access_token - # power bi # sign in to azure in order to authenticate with DefaultAzureCredentials # details here https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet @@ -33,5 +16,7 @@ POWERBI_TABLE_NAME=_test_table_name_here POWERBI_NUMROWS=_num_rows_in_your_test_table -# MongoDB Atlas Vector Search -MONGODB_ATLAS_URI=your_mongodb_atlas_connection_string \ No newline at end of file +# astra db +ASTRA_DB_API_ENDPOINT=https://your_astra_db_id-your_region.apps.astra.datastax.com +ASTRA_DB_APPLICATION_TOKEN=AstraCS:your_astra_db_application_token +# ASTRA_DB_KEYSPACE=your_astra_db_namespace From 5d8c147332de0e99eb72c9e0d317baf93a4c1a62 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Wed, 17 Jan 2024 21:21:18 -0500 Subject: [PATCH 081/215] docs: Document and test PydanticOutputFunctionsParser (#15759) This PR adds documentation and testing to `PydanticOutputFunctionsParser(OutputFunctionsParser)`. --- .../output_parsers/openai_functions.py | 46 +++++++++++++- .../output_parsers/test_openai_functions.py | 61 +++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/libs/langchain/langchain/output_parsers/openai_functions.py b/libs/langchain/langchain/output_parsers/openai_functions.py index 8551706c4d38c..062df8ba967aa 100644 --- a/libs/langchain/langchain/output_parsers/openai_functions.py +++ b/libs/langchain/langchain/output_parsers/openai_functions.py @@ -136,10 +136,52 @@ def parse_result(self, result: List[Generation], *, partial: bool = False) -> An class PydanticOutputFunctionsParser(OutputFunctionsParser): - """Parse an output as a pydantic object.""" + """Parse an output as a pydantic object. + + This parser is used to parse the output of a ChatModel that uses + OpenAI function format to invoke functions. + + The parser extracts the function call invocation and matches + them to the pydantic schema provided. + + An exception will be raised if the function call does not match + the provided schema. + + Example: + + ... code-block:: python + + message = AIMessage( + content="This is a test message", + additional_kwargs={ + "function_call": { + "name": "cookie", + "arguments": json.dumps({"name": "value", "age": 10}), + } + }, + ) + chat_generation = ChatGeneration(message=message) + + class Cookie(BaseModel): + name: str + age: int + + class Dog(BaseModel): + species: str + + # Full output + parser = PydanticOutputFunctionsParser( + pydantic_schema={"cookie": Cookie, "dog": Dog} + ) + result = parser.parse_result([chat_generation]) + """ pydantic_schema: Union[Type[BaseModel], Dict[str, Type[BaseModel]]] - """The pydantic schema to parse the output with.""" + """The pydantic schema to parse the output with. + + If multiple schemas are provided, then the function name will be used to + determine which schema to use. + """ @root_validator(pre=True) def validate_schema(cls, values: Dict) -> Dict: diff --git a/libs/langchain/tests/unit_tests/output_parsers/test_openai_functions.py b/libs/langchain/tests/unit_tests/output_parsers/test_openai_functions.py index 6af2be93c319a..ed1bebf6df5ce 100644 --- a/libs/langchain/tests/unit_tests/output_parsers/test_openai_functions.py +++ b/libs/langchain/tests/unit_tests/output_parsers/test_openai_functions.py @@ -1,3 +1,4 @@ +import json from typing import Any, Dict import pytest @@ -7,7 +8,9 @@ from langchain.output_parsers.openai_functions import ( JsonOutputFunctionsParser, + PydanticOutputFunctionsParser, ) +from langchain.pydantic_v1 import BaseModel def test_json_output_function_parser() -> None: @@ -134,3 +137,61 @@ def test_exceptions_raised_while_parsing(bad_message: BaseMessage) -> None: with pytest.raises(OutputParserException): JsonOutputFunctionsParser().parse_result([chat_generation]) + + +def test_pydantic_output_functions_parser() -> None: + """Test pydantic output functions parser.""" + message = AIMessage( + content="This is a test message", + additional_kwargs={ + "function_call": { + "name": "function_name", + "arguments": json.dumps({"name": "value", "age": 10}), + } + }, + ) + chat_generation = ChatGeneration(message=message) + + class Model(BaseModel): + """Test model.""" + + name: str + age: int + + # Full output + parser = PydanticOutputFunctionsParser(pydantic_schema=Model) + result = parser.parse_result([chat_generation]) + assert result == Model(name="value", age=10) + + +def test_pydantic_output_functions_parser_multiple_schemas() -> None: + """Test that the parser works if providing multiple pydantic schemas.""" + + message = AIMessage( + content="This is a test message", + additional_kwargs={ + "function_call": { + "name": "cookie", + "arguments": json.dumps({"name": "value", "age": 10}), + } + }, + ) + chat_generation = ChatGeneration(message=message) + + class Cookie(BaseModel): + """Test model.""" + + name: str + age: int + + class Dog(BaseModel): + """Test model.""" + + species: str + + # Full output + parser = PydanticOutputFunctionsParser( + pydantic_schema={"cookie": Cookie, "dog": Dog} + ) + result = parser.parse_result([chat_generation]) + assert result == Cookie(name="value", age=10) From 7d444724d7582386de347fb928619c2243bd0e55 Mon Sep 17 00:00:00 2001 From: SN <6432132+samnoyes@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:27:43 -0800 Subject: [PATCH 082/215] Add revision identifier to run_on_dataset (#16167) Allow specifying revision identifier for better project versioning --- .../smith/evaluation/runner_utils.py | 64 ++++++++++++++++--- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/libs/langchain/langchain/smith/evaluation/runner_utils.py b/libs/langchain/langchain/smith/evaluation/runner_utils.py index c79b0fc4c84fb..df04e0f88f610 100644 --- a/libs/langchain/langchain/smith/evaluation/runner_utils.py +++ b/libs/langchain/langchain/smith/evaluation/runner_utils.py @@ -657,6 +657,7 @@ async def _arun_llm( tags: Optional[List[str]] = None, callbacks: Callbacks = None, input_mapper: Optional[Callable[[Dict], Any]] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> Union[str, BaseMessage]: """Asynchronously run the language model. @@ -682,7 +683,9 @@ async def _arun_llm( ): return await llm.ainvoke( prompt_or_messages, - config=RunnableConfig(callbacks=callbacks, tags=tags or []), + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) else: raise InputFormatError( @@ -695,12 +698,18 @@ async def _arun_llm( try: prompt = _get_prompt(inputs) llm_output: Union[str, BaseMessage] = await llm.ainvoke( - prompt, config=RunnableConfig(callbacks=callbacks, tags=tags or []) + prompt, + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) except InputFormatError: messages = _get_messages(inputs) llm_output = await llm.ainvoke( - messages, config=RunnableConfig(callbacks=callbacks, tags=tags or []) + messages, + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) return llm_output @@ -712,6 +721,7 @@ async def _arun_chain( *, tags: Optional[List[str]] = None, input_mapper: Optional[Callable[[Dict], Any]] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> Union[dict, str]: """Run a chain asynchronously on inputs.""" inputs_ = inputs if input_mapper is None else input_mapper(inputs) @@ -723,10 +733,15 @@ async def _arun_chain( ): val = next(iter(inputs_.values())) output = await chain.ainvoke( - val, config=RunnableConfig(callbacks=callbacks, tags=tags or []) + val, + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) else: - runnable_config = RunnableConfig(tags=tags or [], callbacks=callbacks) + runnable_config = RunnableConfig( + tags=tags or [], callbacks=callbacks, metadata=metadata or {} + ) output = await chain.ainvoke(inputs_, config=runnable_config) return output @@ -762,6 +777,7 @@ async def _arun_llm_or_chain( tags=config["tags"], callbacks=config["callbacks"], input_mapper=input_mapper, + metadata=config.get("metadata"), ) else: chain = llm_or_chain_factory() @@ -771,6 +787,7 @@ async def _arun_llm_or_chain( tags=config["tags"], callbacks=config["callbacks"], input_mapper=input_mapper, + metadata=config.get("metadata"), ) result = output except Exception as e: @@ -793,6 +810,7 @@ def _run_llm( *, tags: Optional[List[str]] = None, input_mapper: Optional[Callable[[Dict], Any]] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> Union[str, BaseMessage]: """ Run the language model on the example. @@ -819,7 +837,9 @@ def _run_llm( ): llm_output: Union[str, BaseMessage] = llm.invoke( prompt_or_messages, - config=RunnableConfig(callbacks=callbacks, tags=tags or []), + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) else: raise InputFormatError( @@ -831,12 +851,16 @@ def _run_llm( try: llm_prompts = _get_prompt(inputs) llm_output = llm.invoke( - llm_prompts, config=RunnableConfig(callbacks=callbacks, tags=tags or []) + llm_prompts, + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) except InputFormatError: llm_messages = _get_messages(inputs) llm_output = llm.invoke( - llm_messages, config=RunnableConfig(callbacks=callbacks) + llm_messages, + config=RunnableConfig(callbacks=callbacks, metadata=metadata or {}), ) return llm_output @@ -848,6 +872,7 @@ def _run_chain( *, tags: Optional[List[str]] = None, input_mapper: Optional[Callable[[Dict], Any]] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> Union[Dict, str]: """Run a chain on inputs.""" inputs_ = inputs if input_mapper is None else input_mapper(inputs) @@ -859,10 +884,15 @@ def _run_chain( ): val = next(iter(inputs_.values())) output = chain.invoke( - val, config=RunnableConfig(callbacks=callbacks, tags=tags or []) + val, + config=RunnableConfig( + callbacks=callbacks, tags=tags or [], metadata=metadata or {} + ), ) else: - runnable_config = RunnableConfig(tags=tags or [], callbacks=callbacks) + runnable_config = RunnableConfig( + tags=tags or [], callbacks=callbacks, metadata=metadata or {} + ) output = chain.invoke(inputs_, config=runnable_config) return output @@ -899,6 +929,7 @@ def _run_llm_or_chain( config["callbacks"], tags=config["tags"], input_mapper=input_mapper, + metadata=config.get("metadata"), ) else: chain = llm_or_chain_factory() @@ -908,6 +939,7 @@ def _run_llm_or_chain( config["callbacks"], tags=config["tags"], input_mapper=input_mapper, + metadata=config.get("metadata"), ) result = output except Exception as e: @@ -1083,8 +1115,13 @@ def prepare( input_mapper: Optional[Callable[[Dict], Any]] = None, concurrency_level: int = 5, project_metadata: Optional[Dict[str, Any]] = None, + revision_id: Optional[str] = None, ) -> _DatasetRunContainer: project_name = project_name or name_generation.random_name() + if revision_id: + if not project_metadata: + project_metadata = {} + project_metadata.update({"revision_id": revision_id}) wrapped_model, project, dataset, examples = _prepare_eval_run( client, dataset_name, @@ -1121,6 +1158,7 @@ def prepare( ], tags=tags, max_concurrency=concurrency_level, + metadata={"revision_id": revision_id} if revision_id else {}, ) for example in examples ] @@ -1183,6 +1221,7 @@ async def arun_on_dataset( project_metadata: Optional[Dict[str, Any]] = None, verbose: bool = False, tags: Optional[List[str]] = None, + revision_id: Optional[str] = None, **kwargs: Any, ) -> Dict[str, Any]: input_mapper = kwargs.pop("input_mapper", None) @@ -1208,6 +1247,7 @@ async def arun_on_dataset( input_mapper, concurrency_level, project_metadata=project_metadata, + revision_id=revision_id, ) batch_results = await runnable_utils.gather_with_concurrency( container.configs[0].get("max_concurrency"), @@ -1235,6 +1275,7 @@ def run_on_dataset( project_metadata: Optional[Dict[str, Any]] = None, verbose: bool = False, tags: Optional[List[str]] = None, + revision_id: Optional[str] = None, **kwargs: Any, ) -> Dict[str, Any]: input_mapper = kwargs.pop("input_mapper", None) @@ -1260,6 +1301,7 @@ def run_on_dataset( input_mapper, concurrency_level, project_metadata=project_metadata, + revision_id=revision_id, ) if concurrency_level == 0: batch_results = [ @@ -1309,6 +1351,8 @@ def run_on_dataset( log feedback and run traces. verbose: Whether to print progress. tags: Tags to add to each run in the project. + revision_id: Optional revision identifier to assign this test run to + track the performance of different versions of your system. Returns: A dictionary containing the run's project name and the resulting model outputs. From 27ad65cc680203cccf81a8403d6bba6d0a026f34 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Thu, 18 Jan 2024 07:59:54 -0800 Subject: [PATCH 083/215] docs: add tool use diagrams (#16207) --- docs/docs/use_cases/tool_use/agents.ipynb | 8 +- docs/docs/use_cases/tool_use/index.ipynb | 10 +- docs/docs/use_cases/tool_use/quickstart.ipynb | 6 +- docs/static/img/tool_agent.svg | 104 ++++++ docs/static/img/tool_chain.svg | 306 ++++++++++++++++++ 5 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 docs/static/img/tool_agent.svg create mode 100644 docs/static/img/tool_chain.svg diff --git a/docs/docs/use_cases/tool_use/agents.ipynb b/docs/docs/use_cases/tool_use/agents.ipynb index f542e465a3652..77cbef71d6658 100644 --- a/docs/docs/use_cases/tool_use/agents.ipynb +++ b/docs/docs/use_cases/tool_use/agents.ipynb @@ -13,7 +13,9 @@ { "cell_type": "markdown", "id": "1925a807-fa01-44bc-8a03-d9907311c7f9", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## Agents\n", "\n", @@ -21,7 +23,9 @@ "\n", "LangChain comes with a number of built-in agents that are optimized for different use cases. Read about all the [agent types here](/docs/modules/agents/agent_types/).\n", "\n", - "As an example, let's try out the OpenAI tools agent, which makes use of the new OpenAI tool-calling API (this is only available in the latest OpenAI models, and differs from function-calling in that the model can return multiple function invocations at once):" + "As an example, let's try out the OpenAI tools agent, which makes use of the new OpenAI tool-calling API (this is only available in the latest OpenAI models, and differs from function-calling in that the model can return multiple function invocations at once).\n", + "\n", + "![agent](../../../static/img/tool_agent.svg)" ] }, { diff --git a/docs/docs/use_cases/tool_use/index.ipynb b/docs/docs/use_cases/tool_use/index.ipynb index 0ca033e3c1925..5f85d631bc004 100644 --- a/docs/docs/use_cases/tool_use/index.ipynb +++ b/docs/docs/use_cases/tool_use/index.ipynb @@ -23,7 +23,15 @@ "- A large collection of built-in [Tools](/docs/integrations/tools).\n", "- Provides a lot of flexibility in how you call these tools.\n", "\n", - "There are two main ways to use tools: [chains](/docs/modules/chains) and [agents](/docs/modules/agents/). Chains lets you create a pre-defined sequence of tool usage(s). Agents let the model use tools in a loop, so that it can decide how many times to use tools.\n", + "There are two main ways to use tools: [chains](/docs/modules/chains) and [agents](/docs/modules/agents/). \n", + "\n", + "Chains lets you create a pre-defined sequence of tool usage(s). \n", + "\n", + "![chain](../../../static/img/tool_chain.svg)\n", + "\n", + "Agents let the model use tools in a loop, so that it can decide how many times to use tools.\n", + "\n", + "![agent](../../../static/img/tool_agent.svg)\n", "\n", "To get started with both approaches, head to the [Quickstart](/docs/use_cases/tool_use/quickstart) page." ] diff --git a/docs/docs/use_cases/tool_use/quickstart.ipynb b/docs/docs/use_cases/tool_use/quickstart.ipynb index 3d31c5231bc64..921e9f1a2e3c2 100644 --- a/docs/docs/use_cases/tool_use/quickstart.ipynb +++ b/docs/docs/use_cases/tool_use/quickstart.ipynb @@ -143,6 +143,8 @@ "\n", "If we know that we only need to use a tool a fixed number of times, we can create a chain for doing so. Let's create a simple chain that just multiplies user-specified numbers.\n", "\n", + "![chain](../../../static/img/tool_chain.svg)\n", + "\n", "### Function calling\n", "One of the most reliable ways to use tools with LLMs is with function calling APIs (also sometimes called tool calling or parallel function calling). This only works with models that explicitly support function calling, like OpenAI models.\n", "\n", @@ -332,7 +334,9 @@ "\n", "LangChain comes with a number of built-in agents that are optimized for different use cases. Read about all the [agent types here](/docs/modules/agents/agent_types/).\n", "\n", - "As an example, let's try out the OpenAI tools agent, which makes use of the new OpenAI tool-calling API (this is only available in the latest OpenAI models, and differs from function-calling in that the model can return multiple function invocations at once):" + "As an example, let's try out the OpenAI tools agent, which makes use of the new OpenAI tool-calling API (this is only available in the latest OpenAI models, and differs from function-calling in that the model can return multiple function invocations at once)\n", + "\n", + "![agent](../../../static/img/tool_agent.svg)" ] }, { diff --git a/docs/static/img/tool_agent.svg b/docs/static/img/tool_agent.svg new file mode 100644 index 0000000000000..0992054658df0 --- /dev/null +++ b/docs/static/img/tool_agent.svg @@ -0,0 +1,104 @@ +<svg width="838" height="433" viewBox="0 0 838 433" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_335_10946)"> +<rect width="837" height="433" transform="translate(0.5)" fill="#0F172A"/> +<path d="M19.1952 222.185C24.3952 222.985 29.5285 220.121 31.1952 218.288C29.4773 214.191 40.0831 204.242 33.0831 204.241C31.4645 204.241 30.082 200.001 24.1952 203.185C24.174 204.471 24.1952 208.926 24.1952 209.684C24.1952 220.184 18.1952 221.581 19.1952 222.185Z" fill="#2B354E"/> +<rect x="24.5996" y="188" width="125" height="34" rx="17" fill="#2B354E"/> +<path d="M46.5836 207C46.5836 207.891 46.4369 208.645 46.1436 209.264C45.8503 209.883 45.3916 210.355 44.7676 210.68C44.1489 211 43.3436 211.16 42.3516 211.16C41.3489 211.16 40.5356 210.995 39.9116 210.664C39.2929 210.333 38.8396 209.848 38.5516 209.208C38.2689 208.568 38.1276 207.781 38.1276 206.848V199.432H39.6636V207.072C39.6636 208.005 39.8956 208.704 40.3596 209.168C40.8236 209.632 41.4876 209.864 42.3516 209.864C42.9329 209.864 43.4263 209.763 43.8316 209.56C44.2423 209.357 44.5543 209.051 44.7676 208.64C44.9863 208.224 45.0956 207.701 45.0956 207.072V199.432H46.5836V207ZM51.5547 211.16C50.9787 211.16 50.4427 211.072 49.9467 210.896C49.4561 210.715 49.0481 210.435 48.7227 210.056C48.3974 209.672 48.1921 209.184 48.1067 208.592H49.4507C49.5254 208.917 49.6587 209.189 49.8507 209.408C50.0481 209.621 50.2934 209.784 50.5867 209.896C50.8801 210.003 51.2001 210.056 51.5467 210.056C52.1014 210.056 52.5494 209.952 52.8907 209.744C53.2321 209.536 53.4027 209.216 53.4027 208.784C53.4027 208.48 53.3121 208.237 53.1307 208.056C52.9547 207.869 52.6801 207.733 52.3067 207.648L50.5947 207.224C49.9281 207.064 49.3947 206.813 48.9947 206.472C48.5947 206.131 48.3947 205.653 48.3947 205.04C48.3894 204.555 48.5041 204.128 48.7387 203.76C48.9734 203.392 49.3254 203.101 49.7947 202.888C50.2641 202.675 50.8481 202.568 51.5467 202.568C52.4427 202.568 53.1654 202.771 53.7147 203.176C54.2694 203.576 54.5574 204.165 54.5787 204.944H53.2747C53.2214 204.549 53.0427 204.237 52.7387 204.008C52.4401 203.773 52.0374 203.656 51.5307 203.656C51.0027 203.656 50.5734 203.763 50.2427 203.976C49.9121 204.184 49.7467 204.509 49.7467 204.952C49.7467 205.245 49.8641 205.477 50.0987 205.648C50.3334 205.813 50.6801 205.952 51.1387 206.064L52.7947 206.48C53.1787 206.581 53.4934 206.715 53.7387 206.88C53.9894 207.045 54.1867 207.229 54.3307 207.432C54.4801 207.635 54.5841 207.848 54.6427 208.072C54.7067 208.291 54.7387 208.501 54.7387 208.704C54.7387 209.211 54.6107 209.648 54.3547 210.016C54.1041 210.379 53.7414 210.661 53.2667 210.864C52.7974 211.061 52.2267 211.16 51.5547 211.16ZM57.4542 207.208C57.4542 207.731 57.5396 208.205 57.7102 208.632C57.8862 209.053 58.1502 209.389 58.5022 209.64C58.8596 209.891 59.3049 210.016 59.8382 210.016C60.3662 210.016 60.8196 209.893 61.1982 209.648C61.5822 209.403 61.8276 209.048 61.9342 208.584H63.3342C63.2329 209.144 63.0089 209.616 62.6622 210C62.3156 210.384 61.8969 210.675 61.4062 210.872C60.9156 211.064 60.4036 211.16 59.8702 211.16C59.0969 211.16 58.4169 210.992 57.8302 210.656C57.2436 210.32 56.7849 209.837 56.4542 209.208C56.1236 208.579 55.9582 207.824 55.9582 206.944C55.9582 206.075 56.1076 205.312 56.4062 204.656C56.7049 204 57.1369 203.488 57.7022 203.12C58.2729 202.752 58.9582 202.568 59.7582 202.568C60.5369 202.568 61.1956 202.736 61.7342 203.072C62.2729 203.408 62.6809 203.883 62.9582 204.496C63.2409 205.104 63.3822 205.827 63.3822 206.664V207.208H57.4542ZM57.4622 206.208H61.9582C61.9582 205.733 61.8782 205.307 61.7182 204.928C61.5582 204.544 61.3129 204.243 60.9822 204.024C60.6569 203.8 60.2462 203.688 59.7502 203.688C59.2329 203.688 58.8036 203.813 58.4622 204.064C58.1262 204.309 57.8729 204.627 57.7022 205.016C57.5369 205.4 57.4569 205.797 57.4622 206.208ZM65.0059 211V202.728H66.4139V204.32C66.5525 203.925 66.7472 203.6 66.9979 203.344C67.2485 203.083 67.5339 202.888 67.8539 202.76C68.1792 202.632 68.5152 202.568 68.8619 202.568C68.9845 202.568 69.1045 202.576 69.2219 202.592C69.3392 202.608 69.4299 202.635 69.4939 202.672V204.104C69.4139 204.067 69.3099 204.043 69.1819 204.032C69.0592 204.016 68.9552 204.008 68.8699 204.008C68.5392 203.987 68.2299 204.008 67.9419 204.072C67.6539 204.131 67.4005 204.235 67.1819 204.384C66.9632 204.533 66.7899 204.731 66.6619 204.976C66.5392 205.216 66.4779 205.509 66.4779 205.856V211H65.0059ZM73.7205 205.24C73.7205 203.971 73.9232 202.891 74.3285 202C74.7392 201.109 75.3205 200.432 76.0725 199.968C76.8245 199.504 77.7125 199.272 78.7365 199.272C79.7658 199.272 80.6538 199.504 81.4005 199.968C82.1525 200.432 82.7285 201.109 83.1285 202C83.5338 202.891 83.7365 203.971 83.7365 205.24C83.7365 206.435 83.5578 207.461 83.2005 208.32C82.8485 209.173 82.2752 209.856 81.4805 210.368C81.6778 210.805 81.9685 211.128 82.3525 211.336C82.7418 211.549 83.1738 211.664 83.6485 211.68V212.936C83.2698 212.936 82.8698 212.888 82.4485 212.792C82.0272 212.701 81.6138 212.517 81.2085 212.24C80.8085 211.968 80.4405 211.56 80.1045 211.016C79.8378 211.064 79.5978 211.099 79.3845 211.12C79.1712 211.147 78.9552 211.16 78.7365 211.16C77.7178 211.16 76.8325 210.933 76.0805 210.48C75.3285 210.021 74.7472 209.352 74.3365 208.472C73.9258 207.592 73.7205 206.515 73.7205 205.24ZM75.3605 205.272C75.3605 206.861 75.6538 208.027 76.2405 208.768C76.8325 209.504 77.6645 209.872 78.7365 209.872C79.8138 209.872 80.6458 209.501 81.2325 208.76C81.8245 208.019 82.1205 206.848 82.1205 205.248C82.1205 203.664 81.8272 202.488 81.2405 201.72C80.6592 200.947 79.8245 200.56 78.7365 200.56C77.6538 200.56 76.8192 200.947 76.2325 201.72C75.6512 202.493 75.3605 203.677 75.3605 205.272ZM88.6656 211.16C88.2336 211.155 87.8256 211.093 87.4416 210.976C87.0629 210.853 86.7269 210.675 86.4336 210.44C86.1403 210.205 85.9083 209.915 85.7376 209.568C85.5723 209.216 85.4896 208.808 85.4896 208.344V202.728H86.9696V208.192C86.9696 208.731 87.1349 209.168 87.4656 209.504C87.8016 209.84 88.3056 210.008 88.9776 210.008C89.5856 210.008 90.0736 209.851 90.4416 209.536C90.8149 209.216 91.0016 208.741 91.0016 208.112V202.728H92.4736V211H91.2976L91.1456 209.416C91.0443 209.832 90.8709 210.168 90.6256 210.424C90.3856 210.68 90.0949 210.869 89.7536 210.992C89.4176 211.109 89.0549 211.165 88.6656 211.16ZM95.4299 207.208C95.4299 207.731 95.5152 208.205 95.6859 208.632C95.8619 209.053 96.1259 209.389 96.4779 209.64C96.8352 209.891 97.2805 210.016 97.8139 210.016C98.3419 210.016 98.7952 209.893 99.1739 209.648C99.5579 209.403 99.8032 209.048 99.9099 208.584H101.31C101.209 209.144 100.985 209.616 100.638 210C100.291 210.384 99.8725 210.675 99.3819 210.872C98.8912 211.064 98.3792 211.16 97.8459 211.16C97.0725 211.16 96.3925 210.992 95.8059 210.656C95.2192 210.32 94.7605 209.837 94.4299 209.208C94.0992 208.579 93.9339 207.824 93.9339 206.944C93.9339 206.075 94.0832 205.312 94.3819 204.656C94.6805 204 95.1125 203.488 95.6779 203.12C96.2485 202.752 96.9339 202.568 97.7339 202.568C98.5125 202.568 99.1712 202.736 99.7099 203.072C100.249 203.408 100.657 203.883 100.934 204.496C101.217 205.104 101.358 205.827 101.358 206.664V207.208H95.4299ZM95.4379 206.208H99.9339C99.9339 205.733 99.8539 205.307 99.6939 204.928C99.5339 204.544 99.2885 204.243 98.9579 204.024C98.6325 203.8 98.2219 203.688 97.7259 203.688C97.2085 203.688 96.7792 203.813 96.4379 204.064C96.1019 204.309 95.8485 204.627 95.6779 205.016C95.5125 205.4 95.4325 205.797 95.4379 206.208ZM105.871 211.16C105.295 211.16 104.759 211.072 104.263 210.896C103.772 210.715 103.364 210.435 103.039 210.056C102.713 209.672 102.508 209.184 102.423 208.592H103.767C103.841 208.917 103.975 209.189 104.167 209.408C104.364 209.621 104.609 209.784 104.903 209.896C105.196 210.003 105.516 210.056 105.863 210.056C106.417 210.056 106.865 209.952 107.207 209.744C107.548 209.536 107.719 209.216 107.719 208.784C107.719 208.48 107.628 208.237 107.447 208.056C107.271 207.869 106.996 207.733 106.623 207.648L104.911 207.224C104.244 207.064 103.711 206.813 103.311 206.472C102.911 206.131 102.711 205.653 102.711 205.04C102.705 204.555 102.82 204.128 103.055 203.76C103.289 203.392 103.641 203.101 104.111 202.888C104.58 202.675 105.164 202.568 105.863 202.568C106.759 202.568 107.481 202.771 108.031 203.176C108.585 203.576 108.873 204.165 108.895 204.944H107.591C107.537 204.549 107.359 204.237 107.055 204.008C106.756 203.773 106.353 203.656 105.847 203.656C105.319 203.656 104.889 203.763 104.559 203.976C104.228 204.184 104.063 204.509 104.063 204.952C104.063 205.245 104.18 205.477 104.415 205.648C104.649 205.813 104.996 205.952 105.455 206.064L107.111 206.48C107.495 206.581 107.809 206.715 108.055 206.88C108.305 207.045 108.503 207.229 108.647 207.432C108.796 207.635 108.9 207.848 108.959 208.072C109.023 208.291 109.055 208.501 109.055 208.704C109.055 209.211 108.927 209.648 108.671 210.016C108.42 210.379 108.057 210.661 107.583 210.864C107.113 211.061 106.543 211.16 105.871 211.16ZM114.729 203.808H112.865V208.952C112.865 209.245 112.895 209.461 112.953 209.6C113.017 209.733 113.121 209.819 113.265 209.856C113.415 209.893 113.617 209.912 113.873 209.912H114.777V210.904C114.676 210.941 114.521 210.973 114.313 211C114.111 211.027 113.857 211.04 113.553 211.04C112.983 211.04 112.54 210.963 112.225 210.808C111.911 210.653 111.689 210.424 111.561 210.12C111.439 209.816 111.377 209.443 111.377 209V203.808H110.033V202.728H111.417L111.777 200.32H112.865V202.72H114.729V203.808ZM118.047 202.728V211H116.615V202.728H118.047ZM118.079 199.44V200.976H116.567V199.44H118.079ZM123.53 211.16C122.767 211.16 122.101 210.997 121.53 210.672C120.959 210.341 120.517 209.859 120.202 209.224C119.887 208.589 119.73 207.813 119.73 206.896C119.73 206.032 119.877 205.275 120.17 204.624C120.463 203.973 120.893 203.469 121.458 203.112C122.029 202.749 122.719 202.568 123.53 202.568C124.293 202.568 124.954 202.736 125.514 203.072C126.079 203.403 126.517 203.891 126.826 204.536C127.135 205.181 127.29 205.968 127.29 206.896C127.29 207.739 127.146 208.48 126.858 209.12C126.575 209.76 126.154 210.261 125.594 210.624C125.039 210.981 124.351 211.16 123.53 211.16ZM123.53 209.992C124.026 209.992 124.445 209.867 124.786 209.616C125.127 209.365 125.386 209.005 125.562 208.536C125.738 208.067 125.826 207.509 125.826 206.864C125.826 206.272 125.749 205.741 125.594 205.272C125.439 204.797 125.191 204.421 124.85 204.144C124.514 203.867 124.074 203.728 123.53 203.728C123.029 203.728 122.605 203.853 122.258 204.104C121.911 204.349 121.647 204.707 121.466 205.176C121.29 205.645 121.202 206.208 121.202 206.864C121.202 207.445 121.282 207.973 121.442 208.448C121.602 208.923 121.853 209.299 122.194 209.576C122.535 209.853 122.981 209.992 123.53 209.992ZM128.818 211V202.728H130.25V203.928C130.384 203.699 130.568 203.483 130.802 203.28C131.042 203.077 131.33 202.915 131.666 202.792C132.008 202.669 132.4 202.608 132.842 202.608C133.365 202.608 133.845 202.712 134.282 202.92C134.725 203.128 135.077 203.453 135.338 203.896C135.605 204.333 135.738 204.899 135.738 205.592V211H134.258V205.736C134.258 205.091 134.088 204.613 133.746 204.304C133.41 203.989 132.973 203.832 132.434 203.832C132.061 203.832 131.712 203.893 131.386 204.016C131.061 204.133 130.797 204.315 130.594 204.56C130.392 204.8 130.29 205.104 130.29 205.472V211H128.818Z" fill="white"/> +<path d="M185.5 206L180.5 203.113L180.5 208.887L185.5 206ZM170.5 206.5L181 206.5L181 205.5L170.5 205.5L170.5 206.5Z" fill="#CBD5E1"/> +<rect x="201.5" y="180" width="50" height="50" rx="12" fill="#FF8800" fill-opacity="0.9"/> +<rect x="202" y="180.5" width="49" height="49" rx="11.5" stroke="#FF8800" stroke-opacity="0.1"/> +<g clip-path="url(#clip1_335_10946)"> +<path d="M220.681 194.215C221.68 193.437 222.985 193.007 224.283 193.007C225.059 193.007 225.687 193.274 226.163 193.697C226.291 193.811 226.405 193.935 226.507 194.062C226.609 193.935 226.723 193.811 226.852 193.697C227.328 193.274 227.956 193.007 228.732 193.007C230.029 193.007 231.336 193.437 232.332 194.215C233.11 194.818 233.718 195.652 233.957 196.659C234.461 196.743 234.909 197 235.271 197.339C235.847 197.881 236.259 198.663 236.517 199.468C236.779 200.285 236.91 201.203 236.871 202.085C236.85 202.537 236.784 202.997 236.658 203.438L236.736 203.474C237.18 203.683 237.54 204.01 237.81 204.446C238.32 205.268 238.5 206.451 238.5 207.959C238.5 209.694 237.837 210.871 236.985 211.602C236.541 211.982 236.021 212.264 235.461 212.43C235.257 213.367 234.839 214.245 234.239 214.994C233.371 216.079 232.023 216.996 230.211 216.996C228.759 216.996 227.615 216.192 226.881 215.424C226.75 215.286 226.626 215.143 226.509 214.995C226.39 215.143 226.266 215.286 226.135 215.422C225.401 216.193 224.257 216.996 222.805 216.996C220.992 216.996 219.643 216.079 218.777 214.994C218.176 214.245 217.757 213.368 217.553 212.43C216.992 212.264 216.473 211.982 216.03 211.602C215.178 210.87 214.516 209.694 214.516 207.959C214.516 206.451 214.696 205.268 215.204 204.446C215.474 203.998 215.877 203.646 216.356 203.438C216.233 202.997 216.162 202.543 216.144 202.085C216.104 201.203 216.235 200.285 216.498 199.468C216.756 198.664 217.166 197.881 217.744 197.339C218.105 196.988 218.562 196.751 219.058 196.659C219.298 195.651 219.905 194.818 220.681 194.215ZM221.788 195.634C221.132 196.144 220.748 196.823 220.748 197.606C220.748 197.748 220.714 197.889 220.649 198.016C220.584 198.142 220.49 198.252 220.375 198.335C220.259 198.419 220.125 198.473 219.984 198.495C219.843 198.516 219.699 198.504 219.564 198.459C219.4 198.404 219.226 198.417 218.976 198.652C218.698 198.913 218.416 199.384 218.212 200.018C218.008 200.66 217.917 201.332 217.943 202.005C217.973 202.672 218.131 203.227 218.381 203.601C218.422 203.663 218.456 203.73 218.48 203.8H219.779C220.643 203.8 221.475 204.127 222.108 204.715C222.741 205.303 223.127 206.109 223.191 206.971C223.707 207.177 224.137 207.556 224.405 208.044C224.673 208.532 224.763 209.098 224.659 209.645C224.556 210.192 224.266 210.686 223.839 211.042C223.412 211.399 222.874 211.596 222.317 211.6C221.76 211.603 221.22 211.414 220.788 211.063C220.356 210.712 220.059 210.222 219.949 209.676C219.838 209.131 219.921 208.564 220.182 208.072C220.444 207.581 220.868 207.196 221.382 206.983C221.325 206.598 221.132 206.247 220.838 205.993C220.543 205.74 220.167 205.6 219.779 205.6H216.629C216.449 206.018 216.316 206.739 216.316 207.959C216.316 209.185 216.761 209.857 217.202 210.236C217.686 210.65 218.218 210.758 218.371 210.758C218.61 210.758 218.839 210.853 219.008 211.022C219.176 211.19 219.271 211.419 219.271 211.658C219.271 212.162 219.55 213.078 220.183 213.87C220.793 214.634 221.662 215.196 222.805 215.196C223.57 215.196 224.273 214.766 224.833 214.18C225.105 213.895 225.316 213.598 225.455 213.358C225.508 213.269 225.554 213.175 225.593 213.079L225.6 213.062V201.7H224.525C224.316 202.217 223.934 202.644 223.445 202.91C222.955 203.175 222.388 203.262 221.842 203.156C221.295 203.05 220.802 202.756 220.448 202.327C220.094 201.897 219.9 201.357 219.9 200.8C219.9 200.243 220.094 199.704 220.448 199.274C220.802 198.844 221.295 198.551 221.842 198.444C222.388 198.338 222.955 198.425 223.445 198.691C223.934 198.956 224.316 199.384 224.525 199.9H225.6V196.855L225.597 196.786C225.576 196.417 225.503 196.052 225.381 195.703C225.273 195.415 225.132 195.19 224.967 195.043C224.819 194.911 224.615 194.807 224.283 194.807C223.363 194.807 222.453 195.117 221.787 195.635M227.416 211V213.062L227.422 213.079C227.446 213.142 227.491 213.237 227.561 213.358C227.699 213.598 227.91 213.895 228.183 214.18C228.742 214.766 229.446 215.196 230.211 215.196C231.353 215.196 232.222 214.634 232.833 213.87C233.465 213.078 233.745 212.161 233.745 211.658C233.745 211.419 233.839 211.19 234.008 211.022C234.177 210.853 234.406 210.758 234.645 210.758C234.798 210.758 235.329 210.65 235.813 210.236C236.254 209.857 236.699 209.185 236.699 207.959C236.699 206.51 236.509 205.763 236.28 205.395C236.209 205.268 236.1 205.165 235.968 205.101C235.847 205.043 235.665 205 235.383 205C235.22 205 235.06 204.956 234.92 204.872C234.78 204.789 234.666 204.669 234.589 204.525C234.512 204.381 234.476 204.22 234.484 204.057C234.492 203.894 234.543 203.737 234.634 203.601C234.883 203.227 235.042 202.672 235.073 202.005C235.099 201.332 235.007 200.66 234.803 200.018C234.599 199.384 234.317 198.914 234.04 198.652C233.789 198.417 233.615 198.404 233.452 198.459C233.317 198.504 233.172 198.517 233.031 198.495C232.89 198.474 232.756 198.419 232.641 198.336C232.525 198.253 232.43 198.143 232.365 198.016C232.3 197.889 232.266 197.748 232.266 197.606C232.266 196.823 231.882 196.144 231.227 195.634C230.563 195.117 229.651 194.806 228.731 194.806C228.4 194.806 228.197 194.911 228.048 195.041C227.86 195.226 227.719 195.452 227.635 195.701C227.501 196.071 227.427 196.461 227.416 196.855V209.2H228.179C228.609 209.2 229.021 209.03 229.324 208.726C229.628 208.422 229.799 208.01 229.799 207.58V205.426C229.283 205.217 228.855 204.835 228.589 204.346C228.324 203.856 228.237 203.29 228.343 202.743C228.45 202.196 228.743 201.704 229.173 201.349C229.602 200.995 230.142 200.801 230.699 200.801C231.256 200.801 231.796 200.995 232.225 201.349C232.655 201.704 232.948 202.196 233.055 202.743C233.161 203.29 233.074 203.856 232.809 204.346C232.543 204.835 232.115 205.217 231.599 205.426V207.58C231.599 208.487 231.239 209.357 230.597 209.999C229.956 210.64 229.086 211 228.179 211H227.416ZM222.299 200.2C222.14 200.2 221.987 200.263 221.875 200.376C221.762 200.488 221.699 200.641 221.699 200.8C221.699 200.959 221.762 201.112 221.875 201.224C221.987 201.337 222.14 201.4 222.299 201.4C222.458 201.4 222.611 201.337 222.723 201.224C222.836 201.112 222.899 200.959 222.899 200.8C222.899 200.641 222.836 200.488 222.723 200.376C222.611 200.263 222.458 200.2 222.299 200.2ZM221.699 209.2C221.699 209.359 221.762 209.512 221.875 209.625C221.987 209.737 222.14 209.8 222.299 209.8C222.458 209.8 222.611 209.737 222.723 209.625C222.836 209.512 222.899 209.359 222.899 209.2C222.899 209.041 222.836 208.889 222.723 208.776C222.611 208.664 222.458 208.6 222.299 208.6C222.14 208.6 221.987 208.664 221.875 208.776C221.762 208.889 221.699 209.041 221.699 209.2ZM230.099 203.2C230.099 203.359 230.162 203.512 230.275 203.625C230.387 203.737 230.54 203.8 230.699 203.8C230.858 203.8 231.011 203.737 231.123 203.625C231.236 203.512 231.299 203.359 231.299 203.2C231.299 203.041 231.236 202.888 231.123 202.776C231.011 202.663 230.858 202.6 230.699 202.6C230.54 202.6 230.387 202.663 230.275 202.776C230.162 202.888 230.099 203.041 230.099 203.2Z" fill="white"/> +</g> +<path d="M214.782 240.878H216.301V249.684H220.977V251H214.775L214.782 240.878ZM223.362 240.878H224.881V249.684H229.557V251H223.355L223.362 240.878ZM231.935 251V240.878H234.049L236.898 248.928L239.789 240.878H241.889V251H240.356V242.936L237.556 251H236.247L233.461 242.999V251H231.935Z" fill="white"/> +<path d="M282.5 206L277.5 203.113L277.5 208.887L282.5 206ZM267.5 206.5L278 206.5L278 205.5L267.5 205.5L267.5 206.5Z" fill="#CBD5E1"/> +<rect x="302.5" y="180" width="50" height="50" rx="12" fill="#64748B" fill-opacity="0.4"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M317.643 199.286C317.643 197.471 319.114 196 320.929 196C321.481 196 321.929 196.448 321.929 197C321.929 197.552 321.481 198 320.929 198C320.218 198 319.643 198.575 319.643 199.286V203.857C319.643 204.122 319.538 204.377 319.35 204.564L318.914 205L319.35 205.436C319.538 205.623 319.643 205.878 319.643 206.143V210.714C319.643 211.425 320.218 212 320.929 212C321.481 212 321.929 212.448 321.929 213C321.929 213.552 321.481 214 320.929 214C319.114 214 317.643 212.529 317.643 210.714V206.557L316.793 205.707C316.605 205.52 316.5 205.265 316.5 205C316.5 204.735 316.605 204.48 316.793 204.293L317.643 203.443V199.286ZM333.071 197C333.071 196.448 333.519 196 334.071 196C335.886 196 337.357 197.471 337.357 199.286V203.443L338.207 204.293C338.597 204.683 338.597 205.317 338.207 205.707L337.357 206.557V210.714C337.357 212.53 335.885 214 334.071 214C333.519 214 333.071 213.552 333.071 213C333.071 212.448 333.519 212 334.071 212C334.781 212 335.357 211.424 335.357 210.714V206.143C335.357 205.878 335.462 205.623 335.65 205.436L336.086 205L335.65 204.564C335.462 204.377 335.357 204.122 335.357 203.857V199.286C335.357 198.575 334.782 198 334.071 198C333.519 198 333.071 197.552 333.071 197ZM323.793 201.293C324.183 200.902 324.817 200.902 325.207 201.293L327.5 203.586L329.793 201.293C330.183 200.902 330.817 200.902 331.207 201.293C331.598 201.683 331.598 202.317 331.207 202.707L328.914 205L331.207 207.293C331.598 207.683 331.598 208.317 331.207 208.707C330.817 209.098 330.183 209.098 329.793 208.707L327.5 206.414L325.207 208.707C324.817 209.098 324.183 209.098 323.793 208.707C323.402 208.317 323.402 207.683 323.793 207.293L326.086 205L323.793 202.707C323.402 202.317 323.402 201.683 323.793 201.293Z" fill="white"/> +<path d="M299.886 251V240.878H303.393C304.117 240.878 304.763 241.002 305.332 241.249C305.906 241.496 306.357 241.86 306.683 242.341C307.015 242.822 307.18 243.412 307.18 244.112C307.18 244.784 307.029 245.358 306.725 245.834C306.427 246.305 306.007 246.667 305.465 246.919C304.929 247.166 304.306 247.29 303.596 247.29H301.412V251H299.886ZM301.405 246.065H303.554C304.198 246.065 304.723 245.881 305.129 245.512C305.535 245.139 305.738 244.644 305.738 244.028C305.738 243.403 305.528 242.924 305.108 242.593C304.688 242.257 304.152 242.089 303.498 242.089H301.405V246.065ZM307.738 251L311.392 240.878H313.044L316.691 251H315.179L314.227 248.347H310.23L309.257 251H307.738ZM310.587 246.989H313.842L312.225 242.348L310.587 246.989ZM326.408 251H324.833L323.041 246.765H320.5V251H319.002V240.878H323.02C323.771 240.878 324.394 240.995 324.889 241.228C325.383 241.457 325.752 241.783 325.995 242.208C326.237 242.633 326.359 243.134 326.359 243.713C326.359 244.259 326.265 244.714 326.079 245.078C325.897 245.442 325.659 245.738 325.365 245.967C325.075 246.196 324.77 246.375 324.448 246.506L326.408 251ZM322.614 245.554C323.318 245.554 323.864 245.402 324.252 245.099C324.639 244.791 324.833 244.352 324.833 243.783C324.833 243.223 324.655 242.798 324.301 242.509C323.951 242.22 323.475 242.075 322.873 242.075H320.5V245.554H322.614ZM332.32 251.14C331.839 251.14 331.37 251.079 330.913 250.958C330.456 250.837 330.04 250.657 329.667 250.419C329.294 250.176 328.983 249.875 328.736 249.516C328.493 249.157 328.342 248.737 328.281 248.256H329.842C329.921 248.62 330.078 248.926 330.311 249.173C330.549 249.42 330.841 249.607 331.186 249.733C331.531 249.859 331.907 249.922 332.313 249.922C332.761 249.922 333.162 249.861 333.517 249.74C333.876 249.614 334.161 249.437 334.371 249.208C334.581 248.975 334.686 248.695 334.686 248.368C334.686 248.074 334.614 247.824 334.469 247.619C334.329 247.414 334.128 247.243 333.867 247.108C333.61 246.968 333.3 246.858 332.936 246.779L331.018 246.345C330.267 246.186 329.674 245.892 329.24 245.463C328.811 245.029 328.594 244.455 328.589 243.741C328.584 243.144 328.741 242.621 329.058 242.173C329.38 241.725 329.819 241.377 330.374 241.13C330.929 240.878 331.562 240.752 332.271 240.752C333.097 240.752 333.79 240.89 334.35 241.165C334.915 241.44 335.342 241.804 335.631 242.257C335.92 242.705 336.067 243.193 336.072 243.72H334.539C334.502 243.291 334.376 242.95 334.161 242.698C333.946 242.446 333.676 242.266 333.349 242.159C333.027 242.047 332.672 241.991 332.285 241.991C332.005 241.991 331.737 242.026 331.48 242.096C331.223 242.166 330.997 242.269 330.801 242.404C330.605 242.539 330.449 242.707 330.332 242.908C330.22 243.104 330.164 243.33 330.164 243.587C330.164 243.932 330.281 244.212 330.514 244.427C330.747 244.642 331.177 244.826 331.802 244.98L333.678 245.407C334.215 245.519 334.649 245.685 334.98 245.904C335.311 246.119 335.566 246.364 335.743 246.639C335.92 246.914 336.042 247.201 336.107 247.5C336.172 247.794 336.205 248.076 336.205 248.347C336.205 248.87 336.049 249.343 335.736 249.768C335.423 250.188 334.978 250.522 334.399 250.769C333.82 251.016 333.127 251.14 332.32 251.14ZM338.746 251V240.878H345.284L345.277 242.18H340.251V245.253H344.738V246.534H340.244V249.677L345.403 249.684V251H338.746ZM355.539 251H353.964L352.172 246.765H349.631V251H348.133V240.878H352.151C352.902 240.878 353.525 240.995 354.02 241.228C354.514 241.457 354.883 241.783 355.126 242.208C355.368 242.633 355.49 243.134 355.49 243.713C355.49 244.259 355.396 244.714 355.21 245.078C355.028 245.442 354.79 245.738 354.496 245.967C354.206 246.196 353.901 246.375 353.579 246.506L355.539 251ZM351.745 245.554C352.449 245.554 352.995 245.402 353.383 245.099C353.77 244.791 353.964 244.352 353.964 243.783C353.964 243.223 353.786 242.798 353.432 242.509C353.082 242.22 352.606 242.075 352.004 242.075H349.631V245.554H351.745Z" fill="white"/> +<path d="M387.5 206L382.5 203.113L382.5 208.887L387.5 206ZM372.5 206.5L383 206.5L383 205.5L372.5 205.5L372.5 206.5Z" fill="#CBD5E1"/> +<circle cx="476.5" cy="204.5" r="73.5" fill="#313C57" fill-opacity="0.5"/> +<rect x="411" y="190" width="30" height="30" rx="15" fill="#313C57" fill-opacity="0.8"/> +<g clip-path="url(#clip2_335_10946)"> +<path d="M431.939 204.056L431.877 203.794H426.178V206.206H429.583C429.229 207.885 427.589 208.769 426.249 208.769C425.274 208.769 424.246 208.359 423.565 207.699C423.207 207.346 422.921 206.925 422.725 206.461C422.529 205.997 422.426 205.499 422.422 204.996C422.422 203.98 422.879 202.963 423.544 202.295C424.208 201.626 425.212 201.252 426.209 201.252C427.352 201.252 428.171 201.859 428.477 202.135L430.191 200.43C429.689 199.988 428.307 198.875 426.154 198.875C424.493 198.875 422.9 199.511 421.736 200.672C420.587 201.814 419.992 203.467 419.992 205C419.992 206.533 420.555 208.103 421.668 209.255C422.858 210.483 424.543 211.125 426.278 211.125C427.857 211.125 429.353 210.506 430.419 209.384C431.468 208.28 432.01 206.751 432.01 205.149C432.01 204.474 431.942 204.074 431.939 204.056Z" fill="white" fill-opacity="0.9"/> +</g> +<rect x="451.5" y="180" width="50" height="50" rx="25" fill="#E2E8F0"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M480.5 196C477.739 196 475.5 198.239 475.5 201C475.5 201.411 475.55 201.809 475.642 202.188L475.647 202.208C475.702 202.436 475.751 202.636 475.784 202.798C475.814 202.951 475.851 203.162 475.836 203.382C475.816 203.68 475.754 203.9 475.617 204.165C475.47 204.448 475.222 204.695 475.016 204.899C474.995 204.919 474.975 204.94 474.955 204.959L468.707 211.207C468.269 211.645 468.269 212.355 468.707 212.793C469.145 213.231 469.855 213.231 470.293 212.793L476.541 206.545C476.561 206.525 476.581 206.505 476.601 206.484C476.806 206.279 477.053 206.03 477.335 205.883C477.601 205.746 477.82 205.684 478.119 205.664C478.338 205.649 478.549 205.686 478.703 205.717C478.864 205.749 479.064 205.798 479.293 205.853L479.312 205.858C479.692 205.951 480.09 206 480.5 206C483.262 206 485.5 203.761 485.5 201C485.5 200.812 485.49 200.627 485.47 200.445L483.816 202.098C483.638 202.277 483.464 202.451 483.305 202.586C483.13 202.734 482.908 202.894 482.618 202.988C482.217 203.118 481.784 203.118 481.382 202.988C481.092 202.894 480.871 202.734 480.696 202.586C480.537 202.451 480.363 202.277 480.184 202.098L479.402 201.316C479.223 201.137 479.05 200.964 478.914 200.804C478.766 200.63 478.606 200.408 478.512 200.118C478.382 199.716 478.382 199.284 478.512 198.882C478.606 198.592 478.766 198.37 478.914 198.196C479.05 198.036 479.223 197.863 479.402 197.684L481.056 196.03C480.873 196.01 480.688 196 480.5 196ZM473.5 201C473.5 197.134 476.634 194 480.5 194C481.526 194 482.502 194.221 483.382 194.619C483.681 194.754 483.895 195.028 483.954 195.351C484.013 195.674 483.909 196.005 483.677 196.237L480.839 199.076C480.629 199.285 480.515 199.401 480.439 199.49C480.436 199.493 480.433 199.497 480.431 199.5C480.433 199.503 480.436 199.507 480.439 199.51C480.515 199.599 480.629 199.715 480.839 199.924L481.576 200.662C481.786 200.871 481.901 200.985 481.99 201.061C481.994 201.064 481.997 201.067 482 201.07C482.003 201.067 482.007 201.064 482.01 201.061C482.1 200.985 482.215 200.871 482.424 200.662L485.263 197.823C485.495 197.591 485.826 197.488 486.149 197.546C486.472 197.605 486.746 197.819 486.881 198.118C487.279 198.998 487.5 199.975 487.5 201C487.5 204.866 484.366 208 480.5 208C479.929 208 479.372 207.931 478.838 207.801C478.585 207.74 478.425 207.701 478.308 207.677C478.287 207.673 478.269 207.67 478.255 207.667C478.251 207.671 478.247 207.674 478.243 207.678C478.18 207.734 478.099 207.815 477.955 207.959L471.707 214.207C470.488 215.426 468.512 215.426 467.293 214.207C466.074 212.988 466.074 211.012 467.293 209.793L473.541 203.545C473.685 203.401 473.766 203.32 473.822 203.258C473.826 203.253 473.83 203.249 473.833 203.245C473.83 203.231 473.827 203.213 473.823 203.192C473.799 203.075 473.761 202.915 473.699 202.662C473.569 202.128 473.5 201.571 473.5 201ZM473.85 203.229C473.85 203.229 473.85 203.228 473.85 203.229C473.848 203.233 473.847 203.239 473.845 203.245C473.845 203.246 473.845 203.245 473.845 203.245C473.845 203.246 473.845 203.247 473.844 203.247C473.843 203.253 473.841 203.259 473.84 203.264C473.84 203.264 473.84 203.265 473.84 203.265" fill="#0F172A"/> +<path d="M477 180C480.283 180 483.534 180.647 486.567 181.903C489.6 183.159 492.356 185.001 494.678 187.322C496.999 189.644 498.841 192.4 500.097 195.433C501.353 198.466 502 201.717 502 205C502 208.283 501.353 211.534 500.097 214.567C498.841 217.6 496.999 220.356 494.678 222.678C492.356 224.999 489.6 226.841 486.567 228.097C483.534 229.353 480.283 230 477 230C473.717 230 470.466 229.353 467.433 228.097C464.4 226.841 461.644 224.999 459.322 222.678C457.001 220.356 455.159 217.6 453.903 214.567C452.647 211.534 452 208.283 452 205C452 201.717 452.647 198.466 453.903 195.433C455.159 192.4 457.001 189.644 459.322 187.322C461.644 185.001 464.4 183.159 467.433 181.903C470.466 180.647 473.717 180 477 180L477 180Z" stroke="#3C455A" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M477 180C481.685 180 486.277 181.317 490.25 183.8C494.223 186.283 497.419 189.833 499.472 194.045C501.525 198.257 502.353 202.96 501.862 207.62C501.371 212.28 499.581 216.708 496.695 220.399C493.809 224.09 489.943 226.896 485.54 228.496C481.136 230.097 476.371 230.428 471.789 229.451C467.206 228.474 462.99 226.229 459.622 222.972C456.254 219.715 453.868 215.577 452.738 211.03" stroke="#0A84FF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M461.42 242.119V241.047H467.531V242.119H465.127V249H463.939V242.119H461.42ZM472.301 249.11C471.579 249.11 470.954 248.951 470.426 248.631C469.902 248.309 469.496 247.845 469.21 247.24C468.928 246.631 468.787 245.898 468.787 245.04C468.787 244.175 468.93 243.438 469.216 242.829C469.506 242.217 469.913 241.749 470.437 241.426C470.965 241.1 471.586 240.937 472.301 240.937C473.013 240.937 473.629 241.1 474.149 241.426C474.674 241.749 475.077 242.217 475.359 242.829C475.645 243.438 475.788 244.175 475.788 245.04C475.788 245.898 475.647 246.629 475.365 247.234C475.083 247.839 474.679 248.303 474.155 248.626C473.634 248.949 473.016 249.11 472.301 249.11ZM472.301 248.103C472.763 248.103 473.158 247.997 473.484 247.784C473.814 247.572 474.065 247.24 474.237 246.789C474.41 246.338 474.496 245.76 474.496 245.056C474.496 244.334 474.408 243.744 474.232 243.285C474.06 242.827 473.808 242.49 473.478 242.273C473.152 242.053 472.76 241.943 472.301 241.943C471.843 241.943 471.447 242.053 471.113 242.273C470.783 242.493 470.529 242.833 470.349 243.291C470.169 243.749 470.079 244.338 470.079 245.056C470.079 245.764 470.169 246.343 470.349 246.794C470.529 247.242 470.783 247.572 471.113 247.784C471.447 247.997 471.843 248.103 472.301 248.103ZM480.912 249.11C480.189 249.11 479.564 248.951 479.036 248.631C478.512 248.309 478.107 247.845 477.821 247.24C477.538 246.631 477.397 245.898 477.397 245.04C477.397 244.175 477.54 243.438 477.826 242.829C478.116 242.217 478.523 241.749 479.047 241.426C479.575 241.1 480.197 240.937 480.912 240.937C481.623 240.937 482.239 241.1 482.76 241.426C483.284 241.749 483.687 242.217 483.97 242.829C484.256 243.438 484.399 244.175 484.399 245.04C484.399 245.898 484.258 246.629 483.975 247.234C483.693 247.839 483.29 248.303 482.765 248.626C482.245 248.949 481.627 249.11 480.912 249.11ZM480.912 248.103C481.374 248.103 481.768 247.997 482.094 247.784C482.424 247.572 482.675 247.24 482.848 246.789C483.02 246.338 483.106 245.76 483.106 245.056C483.106 244.334 483.018 243.744 482.842 243.285C482.67 242.827 482.419 242.49 482.089 242.273C481.762 242.053 481.37 241.943 480.912 241.943C480.453 241.943 480.057 242.053 479.724 242.273C479.394 242.493 479.139 242.833 478.959 243.291C478.78 243.749 478.69 244.338 478.69 245.056C478.69 245.764 478.78 246.343 478.959 246.794C479.139 247.242 479.394 247.572 479.724 247.784C480.057 247.997 480.453 248.103 480.912 248.103ZM486.485 241.047H487.679V247.966H491.353V249H486.48L486.485 241.047Z" fill="white" fill-opacity="0.9"/> +<rect x="512" y="190" width="30" height="30" rx="15" fill="#313C57" fill-opacity="0.8"/> +<g clip-path="url(#clip3_335_10946)"> +<path d="M525.893 195.077C523.516 195.377 521.578 196.323 519.924 197.992C518.724 199.2 518.008 200.369 517.508 201.923C517.139 203.062 517.039 203.715 517.039 205C517.039 206.254 517.139 206.931 517.478 208C518.27 210.5 520.085 212.662 522.385 213.839C523.139 214.223 524.224 214.646 524.293 214.585C524.308 214.562 524.131 213.708 523.901 212.677C522.701 207.492 522.001 203.646 522.001 202.369C522.008 201.046 522.57 200.139 523.708 199.585L524.247 199.323L523.431 199.269C522.985 199.239 522.57 199.231 522.501 199.262C522.424 199.285 522.385 199.262 522.385 199.169C522.393 198.962 522.578 198.762 522.962 198.577L523.316 198.408L522.947 198.269C522.27 198.015 522.37 197.985 523.693 198.015C524.839 198.046 525.024 198.069 525.601 198.269C525.954 198.385 526.431 198.615 526.662 198.785C526.893 198.946 527.301 199.146 527.562 199.239C528.508 199.554 529.178 200.1 529.716 201.008C530.193 201.815 530.401 202.577 530.447 203.669C530.478 204.477 530.501 204.615 530.608 204.615C530.678 204.615 531.301 204.439 532.001 204.223C533.354 203.808 533.793 203.769 534.208 204.039C534.662 204.339 534.462 204.608 533.354 205.146C531.785 205.915 530.454 206.269 529.431 206.185C528.624 206.123 528.231 206.254 528.231 206.608C528.231 206.969 528.301 207.069 528.708 207.277C529.416 207.639 530.301 207.646 532.031 207.315C533.185 207.092 533.539 207.108 533.539 207.377C533.539 207.562 533.27 207.792 532.731 208.062C532.047 208.408 531.662 208.492 530.808 208.485C529.77 208.485 528.724 208.262 527.678 207.808C527.554 207.762 527.539 207.808 527.539 208.215C527.539 208.823 527.716 209.746 528.001 210.585C528.124 210.962 528.231 211.269 528.231 211.269C528.231 211.269 528.07 211.262 527.878 211.246C527.67 211.223 527.393 211.269 527.185 211.346L526.839 211.477L526.201 211.2C525.416 210.869 524.916 210.731 524.731 210.8C524.608 210.846 524.601 210.931 524.639 211.831C524.693 212.846 524.831 213.562 525.031 213.754C525.178 213.908 525.378 213.846 526.116 213.423C526.639 213.123 526.762 213.085 526.908 213.154C527.331 213.346 528.093 213.192 528.354 212.869C528.508 212.677 528.531 212.669 528.685 212.769C528.778 212.831 529.024 213.262 529.239 213.731C529.447 214.192 529.631 214.577 529.647 214.592C529.654 214.608 529.893 214.546 530.162 214.454C532.639 213.646 534.854 211.677 535.993 209.285C536.639 207.923 536.931 206.6 536.931 205C536.931 203.854 536.801 202.962 536.493 201.992C535.401 198.523 532.416 195.877 528.847 195.2C528.078 195.054 526.585 194.992 525.893 195.077Z" fill="white" fill-opacity="0.9"/> +<path d="M528.385 200.977C528.238 201.031 528 201.239 528 201.308C528 201.339 528.154 201.308 528.338 201.246C528.615 201.146 528.746 201.139 528.992 201.2C529.362 201.3 529.331 201.192 528.954 201.031C528.685 200.915 528.577 200.908 528.385 200.977Z" fill="white" fill-opacity="0.9"/> +<path d="M523.239 201.361C523.108 201.484 522.993 201.692 522.962 201.846L522.916 202.115L523.085 201.861C523.308 201.523 523.601 201.384 524.093 201.377L524.501 201.369L524.231 201.269C523.793 201.1 523.493 201.131 523.239 201.361Z" fill="white" fill-opacity="0.9"/> +<path d="M528.653 202.654C528.307 202.992 528.461 203.554 528.923 203.654C529.207 203.715 529.507 203.546 529.623 203.254C529.846 202.669 529.1 202.208 528.653 202.654ZM529.446 202.823C529.484 202.954 529.246 203.046 529.138 202.939C529.092 202.892 529.084 202.823 529.115 202.769C529.184 202.662 529.4 202.7 529.446 202.823Z" fill="white" fill-opacity="0.9"/> +<path d="M524.116 202.839C523.662 203.031 523.523 203.616 523.854 203.969C524.1 204.231 524.593 204.231 524.846 203.962C525.123 203.669 525.1 203.269 524.785 202.992C524.531 202.762 524.385 202.731 524.116 202.839ZM524.77 203.223C524.77 203.431 524.623 203.5 524.485 203.362C524.346 203.223 524.416 203.077 524.623 203.077C524.716 203.077 524.77 203.131 524.77 203.223Z" fill="white" fill-opacity="0.9"/> +</g> +<path d="M426 191C427.839 191 429.659 191.362 431.358 192.066C433.056 192.769 434.599 193.8 435.899 195.101C437.2 196.401 438.231 197.944 438.934 199.642C439.638 201.341 440 203.161 440 205C440 206.839 439.638 208.659 438.934 210.358C438.231 212.056 437.2 213.599 435.899 214.899C434.599 216.2 433.056 217.231 431.358 217.934C429.659 218.638 427.839 219 426 219C424.161 219 422.341 218.638 420.642 217.934C418.944 217.231 417.401 216.2 416.101 214.899C414.8 213.599 413.769 212.056 413.066 210.358C412.362 208.659 412 206.838 412 205C412 203.161 412.362 201.341 413.066 199.642C413.769 197.944 414.8 196.401 416.101 195.1C417.401 193.8 418.944 192.769 420.642 192.066C422.341 191.362 424.162 191 426 191L426 191Z" stroke="#3C455A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M426 191C428.212 191 430.393 191.524 432.363 192.53C434.334 193.535 436.038 194.994 437.336 196.785C438.634 198.576 439.49 200.65 439.832 202.835C440.174 205.021 439.993 207.256 439.304 209.359C438.615 211.461 437.438 213.37 435.869 214.93C434.3 216.489 432.384 217.655 430.277 218.331C428.171 219.006 425.934 219.174 423.751 218.818C421.567 218.463 419.499 217.595 417.716 216.286" stroke="#0A84FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<rect x="461.5" y="138" width="30" height="30" rx="15" fill="#313C57" fill-opacity="0.8"/> +<g clip-path="url(#clip4_335_10946)"> +<path d="M482.58 151.712C482.739 151.234 482.794 150.728 482.741 150.227C482.689 149.726 482.53 149.242 482.275 148.808C481.898 148.151 481.322 147.631 480.63 147.323C479.938 147.014 479.166 146.934 478.426 147.093C478.005 146.625 477.468 146.276 476.87 146.08C476.272 145.885 475.633 145.851 475.017 145.981C474.401 146.111 473.83 146.401 473.362 146.822C472.894 147.242 472.544 147.779 472.349 148.377C471.856 148.478 471.389 148.683 470.982 148.979C470.574 149.275 470.234 149.654 469.985 150.092C469.603 150.747 469.44 151.507 469.519 152.262C469.598 153.016 469.915 153.726 470.424 154.288C470.265 154.766 470.209 155.272 470.262 155.773C470.314 156.274 470.472 156.758 470.726 157.192C471.104 157.849 471.681 158.37 472.373 158.678C473.065 158.986 473.838 159.066 474.579 158.908C474.913 159.284 475.324 159.585 475.784 159.79C476.243 159.995 476.742 160.099 477.245 160.097C478.004 160.097 478.744 159.857 479.357 159.41C479.971 158.963 480.426 158.332 480.658 157.609C481.152 157.508 481.618 157.303 482.025 157.007C482.433 156.711 482.773 156.332 483.022 155.894C483.399 155.24 483.56 154.483 483.48 153.731C483.401 152.98 483.086 152.273 482.58 151.712ZM477.245 159.168C476.623 159.169 476.021 158.951 475.544 158.552L475.628 158.505L478.454 156.874C478.524 156.832 478.582 156.773 478.623 156.703C478.664 156.632 478.686 156.552 478.686 156.471V152.487L479.881 153.178C479.886 153.181 479.892 153.185 479.895 153.19C479.899 153.196 479.902 153.202 479.903 153.208V156.51C479.901 157.214 479.621 157.889 479.123 158.388C478.625 158.886 477.95 159.166 477.245 159.168ZM471.532 156.728C471.22 156.19 471.108 155.559 471.216 154.946L471.3 154.996L474.128 156.627C474.198 156.668 474.278 156.69 474.359 156.69C474.44 156.69 474.52 156.668 474.59 156.627L478.045 154.635V156.015C478.045 156.022 478.043 156.029 478.04 156.035C478.036 156.041 478.031 156.047 478.026 156.051L475.164 157.702C474.553 158.054 473.827 158.149 473.146 157.966C472.465 157.784 471.885 157.338 471.532 156.728ZM470.788 150.573C471.102 150.031 471.597 149.618 472.187 149.406V152.764C472.186 152.845 472.206 152.925 472.247 152.995C472.287 153.065 472.346 153.124 472.416 153.164L475.855 155.147L474.66 155.838C474.653 155.842 474.646 155.844 474.639 155.844C474.632 155.844 474.624 155.842 474.618 155.838L471.761 154.191C471.152 153.837 470.707 153.257 470.525 152.576C470.342 151.895 470.437 151.17 470.788 150.559V150.573ZM480.602 152.853L477.153 150.85L478.345 150.162C478.351 150.158 478.358 150.156 478.366 150.156C478.373 150.156 478.38 150.158 478.387 150.162L481.243 151.812C481.68 152.064 482.036 152.435 482.27 152.882C482.503 153.329 482.605 153.833 482.563 154.336C482.521 154.838 482.337 155.318 482.033 155.72C481.728 156.122 481.316 156.429 480.843 156.605V153.248C480.841 153.167 480.817 153.088 480.775 153.019C480.733 152.949 480.673 152.892 480.602 152.853ZM481.791 151.065L481.707 151.015L478.885 149.37C478.814 149.329 478.734 149.307 478.652 149.307C478.571 149.307 478.491 149.329 478.42 149.37L474.968 151.362V149.983C474.967 149.976 474.968 149.969 474.971 149.962C474.974 149.956 474.979 149.95 474.985 149.946L477.841 148.298C478.279 148.046 478.779 147.924 479.284 147.946C479.789 147.968 480.277 148.133 480.691 148.422C481.106 148.711 481.429 149.112 481.624 149.578C481.819 150.044 481.877 150.556 481.792 151.054L481.791 151.065ZM474.316 153.511L473.121 152.822C473.115 152.819 473.11 152.814 473.106 152.808C473.102 152.802 473.1 152.796 473.099 152.789V149.496C473.099 148.991 473.244 148.496 473.515 148.07C473.787 147.644 474.174 147.304 474.631 147.09C475.089 146.876 475.598 146.796 476.099 146.86C476.6 146.925 477.072 147.131 477.461 147.453L477.377 147.501L474.551 149.132C474.481 149.173 474.422 149.232 474.381 149.303C474.341 149.373 474.319 149.453 474.319 149.535L474.316 153.511ZM474.965 152.112L476.504 151.225L478.045 152.112V153.885L476.509 154.772L474.968 153.885L474.965 152.112Z" fill="white"/> +</g> +<rect x="498.865" y="154" width="30" height="30" rx="15" fill="#313C57" fill-opacity="0.8"/> +<g clip-path="url(#clip5_335_10946)"> +<path d="M513.865 159C508.343 159 503.865 163.477 503.865 169C503.865 174.523 508.343 179 513.865 179C519.388 179 523.865 174.523 523.865 169C523.865 163.477 519.388 159 513.865 159ZM519.761 166.673C519.192 167.958 517.453 172.028 516.293 174.681C516.291 174.682 515.989 174.68 515.988 174.68L514.158 170.369C513.432 171.791 512.629 173.27 511.942 174.678C511.937 174.685 511.609 174.681 511.609 174.676C510.558 172.224 509.469 169.788 508.413 167.337C508.168 166.738 507.31 165.775 506.723 165.78C506.723 165.711 506.72 165.555 506.719 165.461L510.337 165.461L510.334 165.774C509.909 165.794 509.175 166.065 509.366 166.534C509.875 167.635 511.681 171.9 512.169 172.983C512.509 172.316 513.46 170.54 513.852 169.789C513.545 169.159 512.531 166.808 512.227 166.216C511.997 165.83 511.422 165.783 510.978 165.776C510.978 165.677 510.984 165.601 510.982 165.465L514.162 165.475V165.764C513.731 165.776 513.324 165.936 513.509 166.348C513.936 167.236 514.186 167.868 514.579 168.689C514.704 168.449 515.347 167.13 515.654 166.434C515.839 165.971 515.562 165.798 514.788 165.777C514.798 165.701 514.791 165.548 514.798 165.475L517.545 165.478L517.546 165.764C517.041 165.784 516.519 166.053 516.246 166.47L514.923 169.213C515.068 169.576 516.34 172.401 516.474 172.714L519.208 166.403C519.013 165.892 518.393 165.777 518.15 165.772C518.152 165.691 518.152 165.566 518.153 165.463L521.007 165.471L521.011 165.485L521.007 165.77C520.38 165.789 519.993 166.124 519.761 166.673Z" fill="white" fill-opacity="0.9"/> +</g> +<rect x="425.5" y="154" width="30" height="30" rx="15" fill="#313C57" fill-opacity="0.8"/> +<path d="M440.486 169.324C440.441 169.388 440.403 169.44 440.367 169.494C439.784 170.351 439.202 171.209 438.619 172.066C438.077 172.864 437.535 173.661 436.992 174.458C436.983 174.47 436.972 174.481 436.958 174.489C436.945 174.496 436.93 174.501 436.915 174.503C436.148 174.496 435.382 174.488 434.616 174.478C434.608 174.478 434.599 174.475 434.582 174.473C434.588 174.457 434.595 174.441 434.604 174.426C435.138 173.597 435.673 172.769 436.207 171.94C436.681 171.206 437.156 170.472 437.631 169.738C438.157 168.923 438.683 168.107 439.208 167.29C439.286 167.17 439.366 167.052 439.44 166.929C439.459 166.896 439.465 166.858 439.456 166.821C439.314 166.369 439.17 165.919 439.026 165.469C438.964 165.276 438.899 165.084 438.842 164.89C438.823 164.828 438.792 164.809 438.728 164.809C438.247 164.811 437.768 164.81 437.288 164.81C437.197 164.81 437.196 164.81 437.196 164.72C437.196 164.13 437.196 163.54 437.195 162.949C437.195 162.884 437.216 162.869 437.278 162.869C438.245 162.871 439.211 162.871 440.178 162.869C440.201 162.866 440.224 162.871 440.242 162.884C440.261 162.897 440.275 162.916 440.28 162.938C440.79 164.22 441.3 165.502 441.81 166.783C442.405 168.278 443 169.772 443.594 171.267C443.779 171.731 443.965 172.195 444.148 172.66C444.17 172.717 444.192 172.727 444.25 172.71C444.756 172.553 445.264 172.401 445.771 172.244C445.834 172.224 445.858 172.237 445.877 172.301C446.05 172.844 446.226 173.385 446.401 173.928C446.407 173.946 446.412 173.965 446.419 173.992C446.383 174.005 446.349 174.02 446.314 174.029C445.162 174.395 444.011 174.76 442.86 175.125C442.805 175.142 442.789 175.124 442.77 175.078C442.419 174.187 442.067 173.297 441.714 172.407C441.368 171.536 441.023 170.664 440.677 169.793C440.625 169.662 440.574 169.531 440.522 169.4C440.514 169.378 440.503 169.358 440.486 169.324Z" fill="white"/> +<path d="M580.5 206L575.5 203.113L575.5 208.887L580.5 206ZM565.5 206.5L576 206.5L576 205.5L565.5 205.5L565.5 206.5Z" fill="#CBD5E1"/> +<rect x="597" y="180" width="50" height="50" rx="12" fill="#E2E8F0"/> +<path d="M620 210.659V213C620 214.105 620.895 215 622 215C623.105 215 624 214.105 624 213V210.659M622 195V196M613 205H612M615.5 198.5L614.9 197.9M628.5 198.5L629.1 197.9M632 205H631M628 205C628 208.314 625.314 211 622 211C618.686 211 616 208.314 616 205C616 201.686 618.686 199 622 199C625.314 199 628 201.686 628 205Z" stroke="#0F172A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M585.13 249.11C584.407 249.11 583.782 248.951 583.254 248.631C582.73 248.309 582.325 247.845 582.039 247.24C581.756 246.631 581.615 245.898 581.615 245.04C581.615 244.175 581.758 243.438 582.044 242.829C582.334 242.217 582.741 241.749 583.265 241.426C583.793 241.1 584.415 240.937 585.13 240.937C585.841 240.937 586.457 241.1 586.978 241.426C587.502 241.749 587.905 242.217 588.188 242.829C588.474 243.438 588.617 244.175 588.617 245.04C588.617 245.898 588.475 246.629 588.193 247.234C587.911 247.839 587.507 248.303 586.983 248.626C586.462 248.949 585.845 249.11 585.13 249.11ZM585.13 248.103C585.592 248.103 585.986 247.997 586.312 247.784C586.642 247.572 586.893 247.24 587.066 246.789C587.238 246.338 587.324 245.76 587.324 245.056C587.324 244.334 587.236 243.744 587.06 243.285C586.888 242.827 586.637 242.49 586.307 242.273C585.98 242.053 585.588 241.943 585.13 241.943C584.671 241.943 584.275 242.053 583.942 242.273C583.612 242.493 583.357 242.833 583.177 243.291C582.997 243.749 582.908 244.338 582.908 245.056C582.908 245.764 582.997 246.343 583.177 246.794C583.357 247.242 583.612 247.572 583.942 247.784C584.275 247.997 584.671 248.103 585.13 248.103ZM590.697 249V241.047H593.343C594.329 241.047 595.072 241.225 595.57 241.58C596.073 241.936 596.324 242.449 596.324 243.12C596.324 243.535 596.216 243.892 595.999 244.193C595.787 244.49 595.413 244.739 594.877 244.941C595.193 245.011 595.457 245.111 595.669 245.243C595.882 245.372 596.049 245.52 596.17 245.689C596.295 245.858 596.383 246.041 596.434 246.239C596.489 246.437 596.516 246.642 596.516 246.855C596.516 247.577 596.262 248.116 595.752 248.472C595.246 248.824 594.474 249 593.436 249H590.697ZM591.874 248.043H593.442C594.058 248.043 594.525 247.938 594.844 247.729C595.163 247.517 595.323 247.2 595.323 246.778C595.323 246.474 595.244 246.224 595.086 246.03C594.929 245.836 594.716 245.693 594.448 245.601C594.181 245.509 593.882 245.463 593.552 245.463H591.874V248.043ZM591.874 244.49H593.552C593.75 244.49 593.944 244.47 594.135 244.429C594.326 244.389 594.5 244.323 594.657 244.231C594.815 244.136 594.942 244.01 595.037 243.852C595.132 243.694 595.18 243.502 595.18 243.274C595.18 242.842 595.017 242.521 594.69 242.312C594.364 242.103 593.941 241.998 593.42 241.998H591.874V244.49ZM601.43 249.11C601.052 249.11 600.684 249.062 600.324 248.967C599.965 248.872 599.639 248.73 599.345 248.543C599.052 248.353 598.808 248.116 598.614 247.834C598.423 247.552 598.304 247.222 598.256 246.844H599.483C599.545 247.13 599.668 247.37 599.851 247.564C600.038 247.759 600.268 247.905 600.539 248.004C600.81 248.103 601.105 248.153 601.424 248.153C601.776 248.153 602.092 248.105 602.37 248.01C602.653 247.911 602.876 247.772 603.041 247.592C603.206 247.409 603.289 247.189 603.289 246.932C603.289 246.701 603.232 246.505 603.118 246.343C603.008 246.182 602.851 246.048 602.645 245.942C602.444 245.832 602.2 245.746 601.914 245.683L600.407 245.342C599.817 245.218 599.351 244.987 599.01 244.649C598.673 244.308 598.502 243.857 598.498 243.296C598.495 242.827 598.618 242.416 598.867 242.064C599.12 241.712 599.465 241.439 599.901 241.245C600.337 241.047 600.834 240.948 601.391 240.948C602.04 240.948 602.585 241.056 603.025 241.272C603.469 241.489 603.804 241.775 604.031 242.13C604.259 242.482 604.374 242.866 604.378 243.28H603.173C603.144 242.943 603.045 242.675 602.876 242.477C602.708 242.279 602.495 242.138 602.238 242.053C601.985 241.965 601.707 241.921 601.402 241.921C601.182 241.921 600.972 241.949 600.77 242.004C600.568 242.059 600.39 242.14 600.236 242.246C600.082 242.352 599.96 242.484 599.868 242.642C599.78 242.796 599.736 242.974 599.736 243.175C599.736 243.447 599.828 243.667 600.011 243.835C600.194 244.004 600.532 244.149 601.023 244.27L602.497 244.605C602.919 244.693 603.26 244.824 603.52 244.996C603.78 245.165 603.98 245.357 604.119 245.573C604.259 245.79 604.354 246.015 604.405 246.25C604.457 246.481 604.482 246.703 604.482 246.915C604.482 247.326 604.36 247.698 604.114 248.032C603.868 248.362 603.518 248.624 603.063 248.819C602.609 249.013 602.064 249.11 601.43 249.11ZM606.586 249V241.047H611.723L611.718 242.07H607.769V244.484H611.294V245.491H607.763V247.96L611.817 247.966V249H606.586ZM619.888 249H618.65L617.242 245.672H615.246V249H614.069V241.047H617.226C617.816 241.047 618.305 241.139 618.694 241.322C619.083 241.502 619.372 241.758 619.563 242.092C619.754 242.426 619.849 242.82 619.849 243.274C619.849 243.703 619.776 244.061 619.629 244.347C619.486 244.633 619.299 244.866 619.068 245.045C618.841 245.225 618.601 245.366 618.348 245.469L619.888 249ZM616.907 244.721C617.46 244.721 617.889 244.602 618.194 244.363C618.498 244.121 618.65 243.777 618.65 243.329C618.65 242.889 618.511 242.556 618.232 242.328C617.957 242.101 617.583 241.987 617.11 241.987H615.246V244.721H616.907ZM627.926 241.047L625.127 249H623.741L620.98 241.047H622.129L624.434 247.647L626.782 241.047H627.926ZM627.988 249L630.859 241.047H632.157L635.023 249H633.835L633.087 246.915H629.946L629.182 249H627.988ZM630.227 245.848H632.784L631.514 242.202L630.227 245.848ZM635.386 242.119V241.047H641.497V242.119H639.093V249H637.905V242.119H635.386ZM643.246 241.047H644.423V249H643.246V241.047ZM650.022 249.11C649.3 249.11 648.675 248.951 648.147 248.631C647.622 248.309 647.217 247.845 646.931 247.24C646.649 246.631 646.508 245.898 646.508 245.04C646.508 244.175 646.651 243.438 646.937 242.829C647.226 242.217 647.633 241.749 648.158 241.426C648.686 241.1 649.307 240.937 650.022 240.937C650.734 240.937 651.35 241.1 651.87 241.426C652.395 241.749 652.798 242.217 653.08 242.829C653.366 243.438 653.509 244.175 653.509 245.04C653.509 245.898 653.368 246.629 653.086 247.234C652.803 247.839 652.4 248.303 651.876 248.626C651.355 248.949 650.737 249.11 650.022 249.11ZM650.022 248.103C650.484 248.103 650.878 247.997 651.205 247.784C651.535 247.572 651.786 247.24 651.958 246.789C652.131 246.338 652.217 245.76 652.217 245.056C652.217 244.334 652.129 243.744 651.953 243.285C651.78 242.827 651.529 242.49 651.199 242.273C650.873 242.053 650.481 241.943 650.022 241.943C649.564 241.943 649.168 242.053 648.834 242.273C648.504 242.493 648.249 242.833 648.07 243.291C647.89 243.749 647.8 244.338 647.8 245.056C647.8 245.764 647.89 246.343 648.07 246.794C648.249 247.242 648.504 247.572 648.834 247.784C649.168 247.997 649.564 248.103 650.022 248.103ZM660.776 241.047H661.959V249H660.903L656.767 242.944V249H655.59V241.047H656.728L660.776 246.976V241.047Z" fill="white" fill-opacity="0.9"/> +<rect x="693.5" y="188" width="120" height="34" rx="17" fill="#0A84FF"/> +<path d="M734.252 211.16C733.222 211.16 732.332 210.928 731.58 210.464C730.828 210 730.246 209.328 729.836 208.448C729.43 207.563 729.228 206.493 729.228 205.24C729.228 203.976 729.433 202.901 729.844 202.016C730.254 201.125 730.836 200.445 731.588 199.976C732.345 199.507 733.233 199.272 734.252 199.272C735.276 199.272 736.161 199.507 736.908 199.976C737.654 200.44 738.23 201.117 738.636 202.008C739.041 202.893 739.244 203.971 739.244 205.24C739.244 206.493 739.041 207.563 738.636 208.448C738.236 209.328 737.662 210 736.916 210.464C736.169 210.928 735.281 211.16 734.252 211.16ZM734.252 209.872C734.966 209.872 735.574 209.709 736.076 209.384C736.577 209.059 736.958 208.555 737.22 207.872C737.486 207.189 737.62 206.317 737.62 205.256C737.62 204.179 737.486 203.293 737.22 202.6C736.953 201.907 736.569 201.395 736.068 201.064C735.566 200.728 734.961 200.56 734.252 200.56C733.548 200.56 732.942 200.728 732.436 201.064C731.929 201.4 731.54 201.915 731.268 202.608C730.996 203.301 730.86 204.184 730.86 205.256C730.86 206.317 730.996 207.189 731.268 207.872C731.54 208.555 731.929 209.059 732.436 209.384C732.942 209.709 733.548 209.872 734.252 209.872ZM743.97 211.16C743.538 211.155 743.13 211.093 742.746 210.976C742.367 210.853 742.031 210.675 741.738 210.44C741.444 210.205 741.212 209.915 741.042 209.568C740.876 209.216 740.794 208.808 740.794 208.344V202.728H742.274V208.192C742.274 208.731 742.439 209.168 742.77 209.504C743.106 209.84 743.61 210.008 744.282 210.008C744.89 210.008 745.378 209.851 745.746 209.536C746.119 209.216 746.306 208.741 746.306 208.112V202.728H747.778V211H746.602L746.45 209.416C746.348 209.832 746.175 210.168 745.93 210.424C745.69 210.68 745.399 210.869 745.058 210.992C744.722 211.109 744.359 211.165 743.97 211.16ZM753.99 203.808H752.126V208.952C752.126 209.245 752.155 209.461 752.214 209.6C752.278 209.733 752.382 209.819 752.526 209.856C752.675 209.893 752.878 209.912 753.134 209.912H754.038V210.904C753.937 210.941 753.782 210.973 753.574 211C753.371 211.027 753.118 211.04 752.814 211.04C752.243 211.04 751.801 210.963 751.486 210.808C751.171 210.653 750.95 210.424 750.822 210.12C750.699 209.816 750.638 209.443 750.638 209V203.808H749.294V202.728H750.678L751.038 200.32H752.126V202.72H753.99V203.808ZM755.708 213.576V202.728H757.18L757.196 204.168C757.292 203.997 757.414 203.819 757.564 203.632C757.718 203.445 757.905 203.272 758.124 203.112C758.348 202.952 758.606 202.821 758.9 202.72C759.198 202.619 759.537 202.568 759.916 202.568C760.577 202.568 761.158 202.72 761.66 203.024C762.166 203.323 762.561 203.781 762.844 204.4C763.126 205.019 763.268 205.803 763.268 206.752C763.268 207.701 763.129 208.504 762.852 209.16C762.58 209.811 762.185 210.307 761.668 210.648C761.15 210.989 760.526 211.16 759.796 211.16C759.428 211.16 759.1 211.107 758.812 211C758.529 210.899 758.281 210.765 758.068 210.6C757.86 210.429 757.684 210.248 757.54 210.056C757.401 209.864 757.286 209.68 757.196 209.504V213.576H755.708ZM759.516 210.008C760.198 210.008 760.756 209.749 761.188 209.232C761.62 208.709 761.836 207.899 761.836 206.8C761.836 205.856 761.638 205.107 761.244 204.552C760.849 203.997 760.273 203.72 759.516 203.72C758.748 203.72 758.166 204.005 757.772 204.576C757.377 205.141 757.18 205.883 757.18 206.8C757.18 207.392 757.268 207.931 757.444 208.416C757.62 208.901 757.881 209.288 758.228 209.576C758.574 209.864 759.004 210.008 759.516 210.008ZM767.715 211.16C767.283 211.155 766.875 211.093 766.491 210.976C766.112 210.853 765.776 210.675 765.483 210.44C765.189 210.205 764.957 209.915 764.787 209.568C764.621 209.216 764.539 208.808 764.539 208.344V202.728H766.019V208.192C766.019 208.731 766.184 209.168 766.515 209.504C766.851 209.84 767.355 210.008 768.027 210.008C768.635 210.008 769.123 209.851 769.491 209.536C769.864 209.216 770.051 208.741 770.051 208.112V202.728H771.523V211H770.347L770.195 209.416C770.093 209.832 769.92 210.168 769.675 210.424C769.435 210.68 769.144 210.869 768.803 210.992C768.467 211.109 768.104 211.165 767.715 211.16ZM777.735 203.808H775.871V208.952C775.871 209.245 775.9 209.461 775.959 209.6C776.023 209.733 776.127 209.819 776.271 209.856C776.42 209.893 776.623 209.912 776.879 209.912H777.783V210.904C777.681 210.941 777.527 210.973 777.319 211C777.116 211.027 776.863 211.04 776.559 211.04C775.988 211.04 775.545 210.963 775.231 210.808C774.916 210.653 774.695 210.424 774.567 210.12C774.444 209.816 774.383 209.443 774.383 209V203.808H773.039V202.728H774.423L774.783 200.32H775.871V202.72H777.735V203.808Z" fill="white"/> +<path d="M817.387 222.185C812.187 222.985 807.054 220.121 805.387 218.288C807.105 214.191 796.499 204.242 803.499 204.241C805.118 204.241 806.5 200.001 812.387 203.185C812.408 204.471 812.387 208.926 812.387 209.684C812.387 220.184 818.387 221.581 817.387 222.185Z" fill="#0A84FF"/> +<path d="M225.785 157L223.398 162L229.172 162L226.785 157L225.785 157ZM491.5 65.5L306.285 65.5L306.285 66.5L491.5 66.5L491.5 65.5ZM225.785 146L225.785 157.5L226.785 157.5L226.785 146L225.785 146ZM306.285 65.5C261.826 65.5 225.785 101.541 225.785 146L226.785 146C226.785 102.093 262.379 66.5 306.285 66.5L306.285 65.5Z" fill="#DBEDFE"/> +<path d="M750.5 170L747.613 165L753.387 165L750.5 170ZM750.5 146L750 146L750.5 146ZM490.5 65.5L670.5 65.5L670.5 66.5L490.5 66.5L490.5 65.5ZM751 146L751 165.5L750 165.5L750 146L751 146ZM670.5 65.5C714.959 65.5 751 101.541 751 146L750 146C750 102.093 714.407 66.5 670.5 66.5L670.5 65.5Z" fill="url(#paint0_linear_335_10946)"/> +<path d="M622 272L624.387 267L618.613 267L621 272L622 272ZM621.5 268L622 268L621.5 268ZM529.5 348.5L530 348.5L530 347.5L529.5 347.5L529.5 348.5ZM533 348.5L534 348.5L534 347.5L533 347.5L533 348.5ZM537 348.5L538 348.5L538 347.5L537 347.5L537 348.5ZM541 348.5L541.5 348.5L541.5 347.5L541 347.5L541 348.5ZM541.5 348.5C541.671 348.5 541.842 348.499 542.012 348.498L542.006 347.498C541.838 347.499 541.669 347.5 541.5 347.5L541.5 348.5ZM545.071 348.422C545.411 348.407 545.75 348.39 546.089 348.371L546.033 347.373C545.698 347.392 545.363 347.409 545.027 347.423L545.071 348.422ZM549.14 348.142C549.479 348.11 549.817 348.076 550.154 348.04L550.048 347.046C549.714 347.082 549.381 347.115 549.046 347.147L549.14 348.142ZM553.19 347.657C553.526 347.608 553.862 347.557 554.198 347.504L554.041 346.517C553.71 346.569 553.378 346.619 553.046 346.668L553.19 347.657ZM557.21 346.968C557.544 346.902 557.877 346.834 558.209 346.764L558.002 345.785C557.674 345.854 557.346 345.922 557.016 345.987L557.21 346.968ZM561.19 346.074C561.52 345.991 561.849 345.906 562.178 345.819L561.921 344.853C561.597 344.939 561.272 345.023 560.946 345.105L561.19 346.074ZM565.119 344.979C565.444 344.88 565.769 344.778 566.093 344.674L565.788 343.722C565.468 343.824 565.147 343.925 564.826 344.023L565.119 344.979ZM568.987 343.685C569.307 343.569 569.626 343.45 569.944 343.33L569.591 342.395C569.277 342.514 568.962 342.63 568.646 342.745L568.987 343.685ZM572.784 342.195C573.098 342.062 573.411 341.928 573.722 341.792L573.322 340.876C573.014 341.01 572.705 341.143 572.395 341.274L572.784 342.195ZM576.501 340.513C576.807 340.365 577.113 340.215 577.417 340.063L576.97 339.168C576.67 339.318 576.368 339.467 576.065 339.613L576.501 340.513ZM580.127 338.644C580.425 338.481 580.723 338.316 581.019 338.148L580.527 337.277C580.235 337.443 579.941 337.606 579.646 337.767L580.127 338.644ZM583.653 336.594C583.943 336.416 584.232 336.236 584.519 336.054L583.984 335.209C583.7 335.388 583.415 335.566 583.129 335.743L583.653 336.594ZM587.071 334.368C587.352 334.175 587.631 333.981 587.908 333.784L587.331 332.968C587.057 333.162 586.781 333.354 586.505 333.544L587.071 334.368ZM590.372 331.972C590.643 331.765 590.911 331.557 591.179 331.347L590.561 330.56C590.297 330.768 590.032 330.974 589.765 331.178L590.372 331.972ZM593.548 329.412C593.808 329.192 594.065 328.971 594.322 328.748L593.665 327.993C593.412 328.214 593.158 328.433 592.901 328.65L593.548 329.412ZM596.591 326.696C596.839 326.464 597.085 326.229 597.33 325.993L596.637 325.273C596.395 325.506 596.152 325.737 595.907 325.967L596.591 326.696ZM599.493 323.83C599.729 323.585 599.964 323.339 600.196 323.091L599.467 322.407C599.237 322.652 599.006 322.895 598.773 323.137L599.493 323.83ZM602.248 320.822C602.471 320.565 602.692 320.308 602.912 320.048L602.15 319.401C601.933 319.658 601.714 319.912 601.493 320.165L602.248 320.822ZM604.847 317.679C605.057 317.411 605.265 317.143 605.472 316.872L604.678 316.265C604.474 316.532 604.268 316.797 604.06 317.061L604.847 317.679ZM607.284 314.408C607.481 314.131 607.675 313.852 607.868 313.571L607.044 313.005C606.854 313.281 606.662 313.557 606.468 313.831L607.284 314.408ZM609.554 311.019C609.736 310.732 609.916 310.443 610.094 310.153L609.243 309.629C609.066 309.915 608.889 310.2 608.709 310.484L609.554 311.019ZM611.648 307.519C611.816 307.223 611.981 306.925 612.144 306.627L611.267 306.146C611.106 306.441 610.943 306.735 610.778 307.027L611.648 307.519ZM613.563 303.917C613.715 303.613 613.865 303.307 614.013 303.001L613.113 302.565C612.967 302.868 612.818 303.17 612.668 303.47L613.563 303.917ZM615.292 300.222C615.428 299.911 615.562 299.598 615.695 299.284L614.774 298.895C614.643 299.205 614.51 299.514 614.376 299.822L615.292 300.222ZM616.83 296.444C616.95 296.126 617.069 295.807 617.185 295.487L616.245 295.146C616.13 295.462 616.014 295.777 615.895 296.091L616.83 296.444ZM618.174 292.593C618.278 292.269 618.38 291.944 618.479 291.619L617.523 291.326C617.425 291.647 617.324 291.968 617.222 292.288L618.174 292.593ZM619.319 288.678C619.406 288.349 619.492 288.02 619.574 287.69L618.605 287.446C618.523 287.772 618.439 288.097 618.353 288.421L619.319 288.678ZM620.264 284.709C620.334 284.377 620.402 284.044 620.468 283.71L619.487 283.516C619.422 283.845 619.354 284.174 619.285 284.502L620.264 284.709ZM621.004 280.698C621.057 280.362 621.108 280.026 621.157 279.69L620.168 279.546C620.119 279.878 620.069 280.21 620.017 280.541L621.004 280.698ZM621.54 276.654C621.576 276.317 621.61 275.979 621.642 275.64L620.647 275.546C620.615 275.881 620.582 276.214 620.546 276.548L621.54 276.654ZM621.871 272.589C621.89 272.25 621.907 271.911 621.922 271.571L620.923 271.527C620.909 271.863 620.892 272.198 620.873 272.533L621.871 272.589ZM621.998 268.512C621.999 268.342 622 268.171 622 268L621 268C621 268.169 620.999 268.338 620.998 268.506L621.998 268.512ZM622 268L622 267.875L621 267.875L621 268L622 268Z" fill="#DBEDFE"/> +<path d="M226.285 267L229.172 272L223.398 272L226.285 267ZM226.285 268L226.785 268L226.285 268ZM529.5 348.5L529.002 348.5L529.002 347.5L529.5 347.5L529.5 348.5ZM526.012 348.5L525.016 348.5L525.016 347.5L526.012 347.5L526.012 348.5ZM522.026 348.5L521.03 348.5L521.03 347.5L522.026 347.5L522.026 348.5ZM518.04 348.5L517.044 348.5L517.044 347.5L518.04 347.5L518.04 348.5ZM514.054 348.5L513.058 348.5L513.058 347.5L514.054 347.5L514.054 348.5ZM510.068 348.5L509.072 348.5L509.072 347.5L510.068 347.5L510.068 348.5ZM506.082 348.5L505.086 348.5L505.086 347.5L506.082 347.5L506.082 348.5ZM502.096 348.5L501.1 348.5L501.1 347.5L502.096 347.5L502.096 348.5ZM498.11 348.5L497.114 348.5L497.114 347.5L498.11 347.5L498.11 348.5ZM494.124 348.5L493.128 348.5L493.128 347.5L494.124 347.5L494.124 348.5ZM490.138 348.5L489.142 348.5L489.142 347.5L490.138 347.5L490.138 348.5ZM486.152 348.5L485.156 348.5L485.156 347.5L486.152 347.5L486.152 348.5ZM482.167 348.5L481.17 348.5L481.17 347.5L482.167 347.5L482.167 348.5ZM478.181 348.5L477.184 348.5L477.184 347.5L478.181 347.5L478.181 348.5ZM474.195 348.5L473.198 348.5L473.198 347.5L474.195 347.5L474.195 348.5ZM470.209 348.5L469.212 348.5L469.212 347.5L470.209 347.5L470.209 348.5ZM466.223 348.5L465.226 348.5L465.226 347.5L466.223 347.5L466.223 348.5ZM462.237 348.5L461.24 348.5L461.24 347.5L462.237 347.5L462.237 348.5ZM458.251 348.5L457.254 348.5L457.254 347.5L458.251 347.5L458.251 348.5ZM454.265 348.5L453.268 348.5L453.268 347.5L454.265 347.5L454.265 348.5ZM450.279 348.5L449.282 348.5L449.282 347.5L450.279 347.5L450.279 348.5ZM446.293 348.5L445.296 348.5L445.296 347.5L446.293 347.5L446.293 348.5ZM442.307 348.5L441.31 348.5L441.31 347.5L442.307 347.5L442.307 348.5ZM438.321 348.5L437.324 348.5L437.324 347.5L438.321 347.5L438.321 348.5ZM434.335 348.5L433.338 348.5L433.338 347.5L434.335 347.5L434.335 348.5ZM430.349 348.5L429.352 348.5L429.352 347.5L430.349 347.5L430.349 348.5ZM426.363 348.5L425.366 348.5L425.366 347.5L426.363 347.5L426.363 348.5ZM422.377 348.5L421.38 348.5L421.38 347.5L422.377 347.5L422.377 348.5ZM418.391 348.5L417.394 348.5L417.394 347.5L418.391 347.5L418.391 348.5ZM414.405 348.5L413.408 348.5L413.408 347.5L414.405 347.5L414.405 348.5ZM410.419 348.5L409.422 348.5L409.422 347.5L410.419 347.5L410.419 348.5ZM406.433 348.5L405.436 348.5L405.436 347.5L406.433 347.5L406.433 348.5ZM402.447 348.5L401.45 348.5L401.45 347.5L402.447 347.5L402.447 348.5ZM398.461 348.5L397.464 348.5L397.464 347.5L398.461 347.5L398.461 348.5ZM394.475 348.5L393.478 348.5L393.478 347.5L394.475 347.5L394.475 348.5ZM390.489 348.5L389.492 348.5L389.492 347.5L390.489 347.5L390.489 348.5ZM386.503 348.5L385.506 348.5L385.506 347.5L386.503 347.5L386.503 348.5ZM382.517 348.5L381.52 348.5L381.52 347.5L382.517 347.5L382.517 348.5ZM378.531 348.5L377.534 348.5L377.534 347.5L378.531 347.5L378.531 348.5ZM374.545 348.5L373.549 348.5L373.549 347.5L374.545 347.5L374.545 348.5ZM370.559 348.5L369.563 348.5L369.563 347.5L370.559 347.5L370.559 348.5ZM366.573 348.5L365.577 348.5L365.577 347.5L366.573 347.5L366.573 348.5ZM362.587 348.5L361.591 348.5L361.591 347.5L362.587 347.5L362.587 348.5ZM358.601 348.5L357.605 348.5L357.605 347.5L358.601 347.5L358.601 348.5ZM354.615 348.5L353.619 348.5L353.619 347.5L354.615 347.5L354.615 348.5ZM350.629 348.5L349.633 348.5L349.633 347.5L350.629 347.5L350.629 348.5ZM346.643 348.5L345.647 348.5L345.647 347.5L346.643 347.5L346.643 348.5ZM342.657 348.5L341.661 348.5L341.661 347.5L342.657 347.5L342.657 348.5ZM338.671 348.5L337.675 348.5L337.675 347.5L338.671 347.5L338.671 348.5ZM334.685 348.5L333.689 348.5L333.689 347.5L334.685 347.5L334.685 348.5ZM330.699 348.5L329.703 348.5L329.703 347.5L330.699 347.5L330.699 348.5ZM326.713 348.5L325.717 348.5L325.717 347.5L326.713 347.5L326.713 348.5ZM322.727 348.5L321.731 348.5L321.731 347.5L322.727 347.5L322.727 348.5ZM318.741 348.5L317.745 348.5L317.745 347.5L318.741 347.5L318.741 348.5ZM314.755 348.5L313.759 348.5L313.759 347.5L314.755 347.5L314.755 348.5ZM310.769 348.5L309.773 348.5L309.773 347.5L310.769 347.5L310.769 348.5ZM306.783 348.5L306.285 348.5L306.285 347.5L306.783 347.5L306.783 348.5ZM306.285 348.5C306.114 348.5 305.943 348.499 305.773 348.498L305.779 347.498C305.948 347.499 306.116 347.5 306.285 347.5L306.285 348.5ZM302.714 348.422C302.374 348.407 302.035 348.39 301.696 348.371L301.753 347.373C302.087 347.392 302.422 347.409 302.758 347.423L302.714 348.422ZM298.645 348.142C298.307 348.11 297.969 348.076 297.631 348.04L297.737 347.046C298.071 347.082 298.404 347.115 298.739 347.147L298.645 348.142ZM294.595 347.657C294.259 347.608 293.923 347.557 293.588 347.504L293.744 346.517C294.075 346.569 294.407 346.619 294.739 346.668L294.595 347.657ZM290.575 346.968C290.241 346.902 289.908 346.834 289.576 346.764L289.783 345.785C290.111 345.854 290.44 345.922 290.769 345.987L290.575 346.968ZM286.595 346.074C286.265 345.992 285.936 345.906 285.608 345.819L285.864 344.853C286.188 344.939 286.513 345.023 286.839 345.105L286.595 346.074ZM282.666 344.979C282.341 344.88 282.016 344.778 281.692 344.674L281.998 343.722C282.317 343.824 282.638 343.925 282.959 344.023L282.666 344.979ZM278.798 343.685C278.478 343.569 278.159 343.45 277.841 343.33L278.194 342.395C278.508 342.514 278.823 342.63 279.14 342.745L278.798 343.685ZM275.001 342.195C274.687 342.062 274.375 341.928 274.063 341.792L274.463 340.876C274.771 341.01 275.08 341.143 275.39 341.274L275.001 342.195ZM271.285 340.513C270.978 340.365 270.673 340.215 270.368 340.063L270.815 339.168C271.116 339.318 271.417 339.467 271.72 339.613L271.285 340.513ZM267.658 338.644C267.36 338.481 267.063 338.316 266.766 338.148L267.258 337.278C267.55 337.443 267.844 337.606 268.139 337.767L267.658 338.644ZM264.132 336.594C263.842 336.416 263.554 336.236 263.266 336.054L263.801 335.209C264.085 335.389 264.37 335.566 264.656 335.743L264.132 336.594ZM260.714 334.368C260.434 334.175 260.155 333.981 259.877 333.784L260.454 332.968C260.728 333.162 261.004 333.354 261.281 333.544L260.714 334.368ZM257.413 331.972C257.143 331.765 256.874 331.557 256.607 331.347L257.224 330.561C257.488 330.768 257.754 330.974 258.02 331.178L257.413 331.972ZM254.237 329.412C253.978 329.192 253.72 328.971 253.463 328.748L254.12 327.993C254.373 328.214 254.628 328.433 254.884 328.65L254.237 329.412ZM251.194 326.696C250.946 326.464 250.7 326.229 250.455 325.993L251.149 325.273C251.39 325.506 251.634 325.737 251.878 325.967L251.194 326.696ZM248.292 323.83C248.056 323.585 247.822 323.339 247.589 323.091L248.318 322.407C248.548 322.652 248.779 322.895 249.012 323.137L248.292 323.83ZM245.537 320.822C245.314 320.566 245.093 320.308 244.873 320.048L245.635 319.401C245.853 319.658 246.071 319.912 246.292 320.165L245.537 320.822ZM242.938 317.679C242.728 317.411 242.52 317.143 242.313 316.872L243.107 316.265C243.312 316.532 243.517 316.797 243.725 317.061L242.938 317.679ZM240.501 314.408C240.305 314.131 240.11 313.852 239.917 313.571L240.741 313.005C240.932 313.281 241.124 313.557 241.317 313.831L240.501 314.408ZM238.232 311.019C238.05 310.732 237.869 310.443 237.691 310.153L238.543 309.629C238.719 309.915 238.897 310.2 239.076 310.484L238.232 311.019ZM236.137 307.519C235.97 307.223 235.804 306.925 235.641 306.627L236.518 306.146C236.679 306.441 236.843 306.735 237.008 307.027L236.137 307.519ZM234.222 303.917C234.07 303.613 233.92 303.307 233.772 303.001L234.672 302.565C234.819 302.868 234.967 303.17 235.117 303.47L234.222 303.917ZM232.493 300.222C232.357 299.911 232.223 299.598 232.09 299.284L233.012 298.895C233.142 299.205 233.275 299.514 233.41 299.822L232.493 300.222ZM230.955 296.444C230.835 296.126 230.716 295.807 230.6 295.487L231.54 295.146C231.655 295.462 231.772 295.777 231.89 296.091L230.955 296.444ZM229.611 292.593C229.507 292.269 229.406 291.944 229.306 291.619L230.262 291.326C230.36 291.647 230.461 291.968 230.563 292.288L229.611 292.593ZM228.466 288.678C228.379 288.349 228.294 288.02 228.211 287.69L229.181 287.446C229.262 287.772 229.346 288.097 229.432 288.421L228.466 288.678ZM227.522 284.709C227.452 284.377 227.383 284.044 227.317 283.71L228.298 283.516C228.364 283.845 228.431 284.174 228.5 284.502L227.522 284.709ZM226.781 280.698C226.728 280.362 226.677 280.026 226.628 279.69L227.617 279.546C227.666 279.878 227.716 280.21 227.769 280.541L226.781 280.698ZM226.245 276.654C226.209 276.317 226.175 275.979 226.143 275.64L227.139 275.546C227.17 275.881 227.204 276.214 227.239 276.548L226.245 276.654ZM225.914 272.589C225.895 272.25 225.878 271.911 225.863 271.571L226.862 271.527C226.877 271.863 226.893 272.198 226.912 272.533L225.914 272.589ZM225.787 268.512C225.786 268.342 225.785 268.171 225.785 268L226.785 268C226.785 268.169 226.786 268.338 226.787 268.506L225.787 268.512ZM225.785 268L225.785 267.875L226.785 267.875L226.785 268L225.785 268Z" fill="url(#paint1_linear_335_10946)"/> +<path d="M440.5 155C442.339 155 444.159 155.362 445.858 156.066C447.556 156.769 449.099 157.8 450.399 159.101C451.7 160.401 452.731 161.944 453.434 163.642C454.138 165.341 454.5 167.161 454.5 169C454.5 170.839 454.138 172.659 453.434 174.358C452.731 176.056 451.7 177.599 450.399 178.899C449.099 180.2 447.556 181.231 445.858 181.934C444.159 182.638 442.339 183 440.5 183C438.661 183 436.841 182.638 435.142 181.934C433.444 181.231 431.901 180.2 430.601 178.899C429.3 177.599 428.269 176.056 427.566 174.358C426.862 172.659 426.5 170.838 426.5 169C426.5 167.161 426.862 165.341 427.566 163.642C428.269 161.944 429.3 160.401 430.601 159.1C431.901 157.8 433.444 156.769 435.142 156.066C436.841 155.362 438.662 155 440.5 155L440.5 155Z" stroke="#3C455A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M440.5 155C443.161 155 445.766 155.758 448.011 157.186C450.257 158.613 452.049 160.651 453.177 163.06" stroke="#0A84FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<g clip-path="url(#clip6_335_10946)"> +<path d="M427.68 43.432H434.888L434.88 44.728H429.2V48.552H434.304V49.832H429.2V55H427.68V43.432ZM438.068 46.728V55H436.636V46.728H438.068ZM438.1 43.44V44.976H436.588V43.44H438.1ZM440.357 55V46.728H441.789V47.928C441.922 47.6987 442.106 47.4827 442.341 47.28C442.581 47.0773 442.869 46.9147 443.205 46.792C443.546 46.6693 443.938 46.608 444.381 46.608C444.904 46.608 445.384 46.712 445.821 46.92C446.264 47.128 446.616 47.4533 446.877 47.896C447.144 48.3333 447.277 48.8987 447.277 49.592V55H445.797V49.736C445.797 49.0907 445.626 48.6133 445.285 48.304C444.949 47.9893 444.512 47.832 443.973 47.832C443.6 47.832 443.25 47.8933 442.925 48.016C442.6 48.1333 442.336 48.3147 442.133 48.56C441.93 48.8 441.829 49.104 441.829 49.472V55H440.357ZM451.44 55.16C450.907 55.16 450.435 55.0667 450.024 54.88C449.613 54.688 449.291 54.4133 449.056 54.056C448.827 53.6987 448.712 53.2667 448.712 52.76C448.712 51.8427 449.032 51.16 449.672 50.712C450.312 50.264 451.339 50.024 452.752 49.992L454.232 49.952V49.304C454.232 48.7973 454.08 48.3973 453.776 48.104C453.472 47.8053 453 47.656 452.36 47.656C451.885 47.6613 451.467 47.7733 451.104 47.992C450.747 48.2107 450.515 48.568 450.408 49.064H449.104C449.136 48.5253 449.288 48.072 449.56 47.704C449.832 47.3307 450.211 47.048 450.696 46.856C451.187 46.664 451.765 46.568 452.432 46.568C453.152 46.568 453.749 46.6693 454.224 46.872C454.704 47.0747 455.061 47.3733 455.296 47.768C455.536 48.1627 455.656 48.648 455.656 49.224V55H454.392L454.288 53.448C453.968 54.0933 453.56 54.5413 453.064 54.792C452.573 55.0373 452.032 55.16 451.44 55.16ZM451.888 54.096C452.165 54.096 452.44 54.0453 452.712 53.944C452.989 53.8427 453.243 53.7067 453.472 53.536C453.701 53.36 453.883 53.1653 454.016 52.952C454.155 52.7333 454.227 52.5093 454.232 52.28V50.864L453 50.888C452.371 50.8987 451.843 50.9627 451.416 51.08C450.995 51.1973 450.677 51.3813 450.464 51.632C450.256 51.8827 450.152 52.2107 450.152 52.616C450.152 53.08 450.312 53.4427 450.632 53.704C450.957 53.9653 451.376 54.096 451.888 54.096ZM459.863 55.104C459.415 55.104 459.053 55.0427 458.775 54.92C458.503 54.7973 458.293 54.632 458.143 54.424C457.999 54.216 457.901 53.9787 457.847 53.712C457.799 53.44 457.775 53.16 457.775 52.872V43.112H459.231V52.728C459.231 53.144 459.311 53.456 459.471 53.664C459.637 53.872 459.866 53.9867 460.159 54.008L460.607 54.016V54.976C460.485 55.0133 460.357 55.0427 460.223 55.064C460.09 55.0907 459.97 55.104 459.863 55.104ZM473.988 55H472.42L470.308 50.016H467.196V55H465.716V43.432H470.196C471.028 43.432 471.724 43.56 472.284 43.816C472.844 44.0667 473.263 44.4293 473.54 44.904C473.818 45.3733 473.956 45.936 473.956 46.592C473.956 47.2213 473.844 47.744 473.62 48.16C473.402 48.576 473.122 48.912 472.78 49.168C472.439 49.4187 472.084 49.6133 471.716 49.752L473.988 55ZM469.716 48.792C470.586 48.792 471.258 48.616 471.732 48.264C472.207 47.9067 472.444 47.3787 472.444 46.68C472.444 45.9973 472.231 45.488 471.804 45.152C471.383 44.8107 470.804 44.64 470.068 44.64H467.196V48.792H469.716ZM476.623 51.208C476.623 51.7307 476.708 52.2053 476.879 52.632C477.055 53.0533 477.319 53.3893 477.671 53.64C478.028 53.8907 478.474 54.016 479.007 54.016C479.535 54.016 479.988 53.8933 480.367 53.648C480.751 53.4027 480.996 53.048 481.103 52.584H482.503C482.402 53.144 482.178 53.616 481.831 54C481.484 54.384 481.066 54.6747 480.575 54.872C480.084 55.064 479.572 55.16 479.039 55.16C478.266 55.16 477.586 54.992 476.999 54.656C476.412 54.32 475.954 53.8373 475.623 53.208C475.292 52.5787 475.127 51.824 475.127 50.944C475.127 50.0747 475.276 49.312 475.575 48.656C475.874 48 476.306 47.488 476.871 47.12C477.442 46.752 478.127 46.568 478.927 46.568C479.706 46.568 480.364 46.736 480.903 47.072C481.442 47.408 481.85 47.8827 482.127 48.496C482.41 49.104 482.551 49.8267 482.551 50.664V51.208H476.623ZM476.631 50.208H481.127C481.127 49.7333 481.047 49.3067 480.887 48.928C480.727 48.544 480.482 48.2427 480.151 48.024C479.826 47.8 479.415 47.688 478.919 47.688C478.402 47.688 477.972 47.8133 477.631 48.064C477.295 48.3093 477.042 48.6267 476.871 49.016C476.706 49.4 476.626 49.7973 476.631 50.208ZM487.064 55.16C486.488 55.16 485.952 55.072 485.456 54.896C484.965 54.7147 484.557 54.4347 484.232 54.056C483.906 53.672 483.701 53.184 483.616 52.592H484.96C485.034 52.9173 485.168 53.1893 485.36 53.408C485.557 53.6213 485.802 53.784 486.096 53.896C486.389 54.0027 486.709 54.056 487.056 54.056C487.61 54.056 488.058 53.952 488.4 53.744C488.741 53.536 488.912 53.216 488.912 52.784C488.912 52.48 488.821 52.2373 488.64 52.056C488.464 51.8693 488.189 51.7333 487.816 51.648L486.104 51.224C485.437 51.064 484.904 50.8133 484.504 50.472C484.104 50.1307 483.904 49.6533 483.904 49.04C483.898 48.5547 484.013 48.128 484.248 47.76C484.482 47.392 484.834 47.1013 485.304 46.888C485.773 46.6747 486.357 46.568 487.056 46.568C487.952 46.568 488.674 46.7707 489.224 47.176C489.778 47.576 490.066 48.1653 490.088 48.944H488.784C488.73 48.5493 488.552 48.2373 488.248 48.008C487.949 47.7733 487.546 47.656 487.04 47.656C486.512 47.656 486.082 47.7627 485.752 47.976C485.421 48.184 485.256 48.5093 485.256 48.952C485.256 49.2453 485.373 49.4773 485.608 49.648C485.842 49.8133 486.189 49.952 486.648 50.064L488.304 50.48C488.688 50.5813 489.002 50.7147 489.248 50.88C489.498 51.0453 489.696 51.2293 489.84 51.432C489.989 51.6347 490.093 51.848 490.152 52.072C490.216 52.2907 490.248 52.5013 490.248 52.704C490.248 53.2107 490.12 53.648 489.864 54.016C489.613 54.3787 489.25 54.6613 488.776 54.864C488.306 55.0613 487.736 55.16 487.064 55.16ZM491.907 57.576V46.728H493.379L493.395 48.168C493.491 47.9973 493.614 47.8187 493.763 47.632C493.918 47.4453 494.104 47.272 494.323 47.112C494.547 46.952 494.806 46.8213 495.099 46.72C495.398 46.6187 495.736 46.568 496.115 46.568C496.776 46.568 497.358 46.72 497.859 47.024C498.366 47.3227 498.76 47.7813 499.043 48.4C499.326 49.0187 499.467 49.8027 499.467 50.752C499.467 51.7013 499.328 52.504 499.051 53.16C498.779 53.8107 498.384 54.3067 497.867 54.648C497.35 54.9893 496.726 55.16 495.995 55.16C495.627 55.16 495.299 55.1067 495.011 55C494.728 54.8987 494.48 54.7653 494.267 54.6C494.059 54.4293 493.883 54.248 493.739 54.056C493.6 53.864 493.486 53.68 493.395 53.504V57.576H491.907ZM495.715 54.008C496.398 54.008 496.955 53.7493 497.387 53.232C497.819 52.7093 498.035 51.8987 498.035 50.8C498.035 49.856 497.838 49.1067 497.443 48.552C497.048 47.9973 496.472 47.72 495.715 47.72C494.947 47.72 494.366 48.0053 493.971 48.576C493.576 49.1413 493.379 49.8827 493.379 50.8C493.379 51.392 493.467 51.9307 493.643 52.416C493.819 52.9013 494.08 53.288 494.427 53.576C494.774 53.864 495.203 54.008 495.715 54.008ZM504.287 55.16C503.524 55.16 502.858 54.9973 502.287 54.672C501.716 54.3413 501.274 53.8587 500.959 53.224C500.644 52.5893 500.487 51.8133 500.487 50.896C500.487 50.032 500.634 49.2747 500.927 48.624C501.22 47.9733 501.65 47.4693 502.215 47.112C502.786 46.7493 503.476 46.568 504.287 46.568C505.05 46.568 505.711 46.736 506.271 47.072C506.836 47.4027 507.274 47.8907 507.583 48.536C507.892 49.1813 508.047 49.968 508.047 50.896C508.047 51.7387 507.903 52.48 507.615 53.12C507.332 53.76 506.911 54.2613 506.351 54.624C505.796 54.9813 505.108 55.16 504.287 55.16ZM504.287 53.992C504.783 53.992 505.202 53.8667 505.543 53.616C505.884 53.3653 506.143 53.0053 506.319 52.536C506.495 52.0667 506.583 51.5093 506.583 50.864C506.583 50.272 506.506 49.7413 506.351 49.272C506.196 48.7973 505.948 48.4213 505.607 48.144C505.271 47.8667 504.831 47.728 504.287 47.728C503.786 47.728 503.362 47.8533 503.015 48.104C502.668 48.3493 502.404 48.7067 502.223 49.176C502.047 49.6453 501.959 50.208 501.959 50.864C501.959 51.4453 502.039 51.9733 502.199 52.448C502.359 52.9227 502.61 53.2987 502.951 53.576C503.292 53.8533 503.738 53.992 504.287 53.992ZM509.576 55V46.728H511.008V47.928C511.141 47.6987 511.325 47.4827 511.56 47.28C511.8 47.0773 512.088 46.9147 512.424 46.792C512.765 46.6693 513.157 46.608 513.6 46.608C514.122 46.608 514.602 46.712 515.04 46.92C515.482 47.128 515.834 47.4533 516.096 47.896C516.362 48.3333 516.496 48.8987 516.496 49.592V55H515.016V49.736C515.016 49.0907 514.845 48.6133 514.504 48.304C514.168 47.9893 513.73 47.832 513.192 47.832C512.818 47.832 512.469 47.8933 512.144 48.016C511.818 48.1333 511.554 48.3147 511.352 48.56C511.149 48.8 511.048 49.104 511.048 49.472V55H509.576ZM521.213 55.16C520.637 55.16 520.101 55.072 519.605 54.896C519.114 54.7147 518.706 54.4347 518.381 54.056C518.056 53.672 517.85 53.184 517.765 52.592H519.109C519.184 52.9173 519.317 53.1893 519.509 53.408C519.706 53.6213 519.952 53.784 520.245 53.896C520.538 54.0027 520.858 54.056 521.205 54.056C521.76 54.056 522.208 53.952 522.549 53.744C522.89 53.536 523.061 53.216 523.061 52.784C523.061 52.48 522.97 52.2373 522.789 52.056C522.613 51.8693 522.338 51.7333 521.965 51.648L520.253 51.224C519.586 51.064 519.053 50.8133 518.653 50.472C518.253 50.1307 518.053 49.6533 518.053 49.04C518.048 48.5547 518.162 48.128 518.397 47.76C518.632 47.392 518.984 47.1013 519.453 46.888C519.922 46.6747 520.506 46.568 521.205 46.568C522.101 46.568 522.824 46.7707 523.373 47.176C523.928 47.576 524.216 48.1653 524.237 48.944H522.933C522.88 48.5493 522.701 48.2373 522.397 48.008C522.098 47.7733 521.696 47.656 521.189 47.656C520.661 47.656 520.232 47.7627 519.901 47.976C519.57 48.184 519.405 48.5093 519.405 48.952C519.405 49.2453 519.522 49.4773 519.757 49.648C519.992 49.8133 520.338 49.952 520.797 50.064L522.453 50.48C522.837 50.5813 523.152 50.7147 523.397 50.88C523.648 51.0453 523.845 51.2293 523.989 51.432C524.138 51.6347 524.242 51.848 524.301 52.072C524.365 52.2907 524.397 52.5013 524.397 52.704C524.397 53.2107 524.269 53.648 524.013 54.016C523.762 54.3787 523.4 54.6613 522.925 54.864C522.456 55.0613 521.885 55.16 521.213 55.16ZM527.112 51.208C527.112 51.7307 527.198 52.2053 527.368 52.632C527.544 53.0533 527.808 53.3893 528.16 53.64C528.518 53.8907 528.963 54.016 529.496 54.016C530.024 54.016 530.478 53.8933 530.856 53.648C531.24 53.4027 531.486 53.048 531.592 52.584H532.992C532.891 53.144 532.667 53.616 532.32 54C531.974 54.384 531.555 54.6747 531.064 54.872C530.574 55.064 530.062 55.16 529.528 55.16C528.755 55.16 528.075 54.992 527.488 54.656C526.902 54.32 526.443 53.8373 526.112 53.208C525.782 52.5787 525.616 51.824 525.616 50.944C525.616 50.0747 525.766 49.312 526.064 48.656C526.363 48 526.795 47.488 527.36 47.12C527.931 46.752 528.616 46.568 529.416 46.568C530.195 46.568 530.854 46.736 531.392 47.072C531.931 47.408 532.339 47.8827 532.616 48.496C532.899 49.104 533.04 49.8267 533.04 50.664V51.208H527.112ZM527.12 50.208H531.616C531.616 49.7333 531.536 49.3067 531.376 48.928C531.216 48.544 530.971 48.2427 530.64 48.024C530.315 47.8 529.904 47.688 529.408 47.688C528.891 47.688 528.462 47.8133 528.12 48.064C527.784 48.3093 527.531 48.6267 527.36 49.016C527.195 49.4 527.115 49.7973 527.12 50.208Z" fill="white"/> +</g> +<g clip-path="url(#clip7_335_10946)"> +<path d="M339.908 360.432H341.42V370.672H346.884V372H339.9L339.908 360.432ZM351.507 372.16C350.744 372.16 350.078 371.997 349.507 371.672C348.936 371.341 348.494 370.859 348.179 370.224C347.864 369.589 347.707 368.813 347.707 367.896C347.707 367.032 347.854 366.275 348.147 365.624C348.44 364.973 348.87 364.469 349.435 364.112C350.006 363.749 350.696 363.568 351.507 363.568C352.27 363.568 352.931 363.736 353.491 364.072C354.056 364.403 354.494 364.891 354.803 365.536C355.112 366.181 355.267 366.968 355.267 367.896C355.267 368.739 355.123 369.48 354.835 370.12C354.552 370.76 354.131 371.261 353.571 371.624C353.016 371.981 352.328 372.16 351.507 372.16ZM351.507 370.992C352.003 370.992 352.422 370.867 352.763 370.616C353.104 370.365 353.363 370.005 353.539 369.536C353.715 369.067 353.803 368.509 353.803 367.864C353.803 367.272 353.726 366.741 353.571 366.272C353.416 365.797 353.168 365.421 352.827 365.144C352.491 364.867 352.051 364.728 351.507 364.728C351.006 364.728 350.582 364.853 350.235 365.104C349.888 365.349 349.624 365.707 349.443 366.176C349.267 366.645 349.179 367.208 349.179 367.864C349.179 368.445 349.259 368.973 349.419 369.448C349.579 369.923 349.83 370.299 350.171 370.576C350.512 370.853 350.958 370.992 351.507 370.992ZM360.083 372.16C359.321 372.16 358.654 371.997 358.083 371.672C357.513 371.341 357.07 370.859 356.755 370.224C356.441 369.589 356.283 368.813 356.283 367.896C356.283 367.032 356.43 366.275 356.723 365.624C357.017 364.973 357.446 364.469 358.011 364.112C358.582 363.749 359.273 363.568 360.083 363.568C360.846 363.568 361.507 363.736 362.067 364.072C362.633 364.403 363.07 364.891 363.379 365.536C363.689 366.181 363.843 366.968 363.843 367.896C363.843 368.739 363.699 369.48 363.411 370.12C363.129 370.76 362.707 371.261 362.147 371.624C361.593 371.981 360.905 372.16 360.083 372.16ZM360.083 370.992C360.579 370.992 360.998 370.867 361.339 370.616C361.681 370.365 361.939 370.005 362.115 369.536C362.291 369.067 362.379 368.509 362.379 367.864C362.379 367.272 362.302 366.741 362.147 366.272C361.993 365.797 361.745 365.421 361.403 365.144C361.067 364.867 360.627 364.728 360.083 364.728C359.582 364.728 359.158 364.853 358.811 365.104C358.465 365.349 358.201 365.707 358.019 366.176C357.843 366.645 357.755 367.208 357.755 367.864C357.755 368.445 357.835 368.973 357.995 369.448C358.155 369.923 358.406 370.299 358.747 370.576C359.089 370.853 359.534 370.992 360.083 370.992ZM365.372 374.576V363.728H366.844L366.86 365.168C366.956 364.997 367.078 364.819 367.228 364.632C367.382 364.445 367.569 364.272 367.788 364.112C368.012 363.952 368.27 363.821 368.564 363.72C368.862 363.619 369.201 363.568 369.58 363.568C370.241 363.568 370.822 363.72 371.324 364.024C371.83 364.323 372.225 364.781 372.508 365.4C372.79 366.019 372.932 366.803 372.932 367.752C372.932 368.701 372.793 369.504 372.516 370.16C372.244 370.811 371.849 371.307 371.332 371.648C370.814 371.989 370.19 372.16 369.46 372.16C369.092 372.16 368.764 372.107 368.476 372C368.193 371.899 367.945 371.765 367.732 371.6C367.524 371.429 367.348 371.248 367.204 371.056C367.065 370.864 366.95 370.68 366.86 370.504V374.576H365.372ZM369.18 371.008C369.862 371.008 370.42 370.749 370.852 370.232C371.284 369.709 371.5 368.899 371.5 367.8C371.5 366.856 371.302 366.107 370.908 365.552C370.513 364.997 369.937 364.72 369.18 364.72C368.412 364.72 367.83 365.005 367.436 365.576C367.041 366.141 366.844 366.883 366.844 367.8C366.844 368.392 366.932 368.931 367.108 369.416C367.284 369.901 367.545 370.288 367.892 370.576C368.238 370.864 368.668 371.008 369.18 371.008ZM380.689 372.16C380.257 372.155 379.849 372.093 379.465 371.976C379.087 371.853 378.751 371.675 378.457 371.44C378.164 371.205 377.932 370.915 377.761 370.568C377.596 370.216 377.513 369.808 377.513 369.344V363.728H378.993V369.192C378.993 369.731 379.159 370.168 379.489 370.504C379.825 370.84 380.329 371.008 381.001 371.008C381.609 371.008 382.097 370.851 382.465 370.536C382.839 370.216 383.025 369.741 383.025 369.112V363.728H384.497V372H383.321L383.169 370.416C383.068 370.832 382.895 371.168 382.649 371.424C382.409 371.68 382.119 371.869 381.777 371.992C381.441 372.109 381.079 372.165 380.689 372.16ZM386.491 372V363.728H387.923V364.928C388.057 364.699 388.241 364.483 388.475 364.28C388.715 364.077 389.003 363.915 389.339 363.792C389.681 363.669 390.073 363.608 390.515 363.608C391.038 363.608 391.518 363.712 391.955 363.92C392.398 364.128 392.75 364.453 393.011 364.896C393.278 365.333 393.411 365.899 393.411 366.592V372H391.931V366.736C391.931 366.091 391.761 365.613 391.419 365.304C391.083 364.989 390.646 364.832 390.107 364.832C389.734 364.832 389.385 364.893 389.059 365.016C388.734 365.133 388.47 365.315 388.267 365.56C388.065 365.8 387.963 366.104 387.963 366.472V372H386.491ZM399.13 364.808H397.266V369.952C397.266 370.245 397.295 370.461 397.354 370.6C397.418 370.733 397.522 370.819 397.666 370.856C397.815 370.893 398.018 370.912 398.274 370.912H399.178V371.904C399.076 371.941 398.922 371.973 398.714 372C398.511 372.027 398.258 372.04 397.954 372.04C397.383 372.04 396.94 371.963 396.626 371.808C396.311 371.653 396.09 371.424 395.962 371.12C395.839 370.816 395.778 370.443 395.778 370V364.808H394.434V363.728H395.818L396.178 361.32H397.266V363.72H399.13V364.808ZM402.447 363.728V372H401.015V363.728H402.447ZM402.479 360.44V361.976H400.967V360.44H402.479ZM406.824 372.104C406.376 372.104 406.014 372.043 405.736 371.92C405.464 371.797 405.254 371.632 405.104 371.424C404.96 371.216 404.862 370.979 404.808 370.712C404.76 370.44 404.736 370.16 404.736 369.872V360.112H406.192V369.728C406.192 370.144 406.272 370.456 406.432 370.664C406.598 370.872 406.827 370.987 407.12 371.008L407.568 371.016V371.976C407.446 372.013 407.318 372.043 407.184 372.064C407.051 372.091 406.931 372.104 406.824 372.104ZM416.952 363.728V364.808H414.944V372H413.456V364.808H411.712V363.728H413.456V362.448C413.456 361.792 413.624 361.293 413.96 360.952C414.296 360.605 414.797 360.432 415.464 360.432H417.024L417.032 361.48H415.792C415.456 361.48 415.226 361.571 415.104 361.752C414.981 361.933 414.92 362.208 414.92 362.576V363.728H416.952ZM420.376 363.728V372H418.944V363.728H420.376ZM420.408 360.44V361.976H418.896V360.44H420.408ZM422.668 372V363.728H424.1V364.928C424.234 364.699 424.418 364.483 424.652 364.28C424.892 364.077 425.18 363.915 425.516 363.792C425.858 363.669 426.25 363.608 426.692 363.608C427.215 363.608 427.695 363.712 428.132 363.92C428.575 364.128 428.927 364.453 429.188 364.896C429.455 365.333 429.588 365.899 429.588 366.592V372H428.108V366.736C428.108 366.091 427.938 365.613 427.596 365.304C427.26 364.989 426.823 364.832 426.284 364.832C425.911 364.832 425.562 364.893 425.236 365.016C424.911 365.133 424.647 365.315 424.444 365.56C424.242 365.8 424.14 366.104 424.14 366.472V372H422.668ZM433.752 372.16C433.218 372.16 432.746 372.067 432.336 371.88C431.925 371.688 431.602 371.413 431.368 371.056C431.138 370.699 431.024 370.267 431.024 369.76C431.024 368.843 431.344 368.16 431.984 367.712C432.624 367.264 433.65 367.024 435.064 366.992L436.544 366.952V366.304C436.544 365.797 436.392 365.397 436.088 365.104C435.784 364.805 435.312 364.656 434.672 364.656C434.197 364.661 433.778 364.773 433.416 364.992C433.058 365.211 432.826 365.568 432.72 366.064H431.416C431.448 365.525 431.6 365.072 431.872 364.704C432.144 364.331 432.522 364.048 433.008 363.856C433.498 363.664 434.077 363.568 434.744 363.568C435.464 363.568 436.061 363.669 436.536 363.872C437.016 364.075 437.373 364.373 437.608 364.768C437.848 365.163 437.968 365.648 437.968 366.224V372H436.704L436.6 370.448C436.28 371.093 435.872 371.541 435.376 371.792C434.885 372.037 434.344 372.16 433.752 372.16ZM434.2 371.096C434.477 371.096 434.752 371.045 435.024 370.944C435.301 370.843 435.554 370.707 435.784 370.536C436.013 370.36 436.194 370.165 436.328 369.952C436.466 369.733 436.538 369.509 436.544 369.28V367.864L435.312 367.888C434.682 367.899 434.154 367.963 433.728 368.08C433.306 368.197 432.989 368.381 432.776 368.632C432.568 368.883 432.464 369.211 432.464 369.616C432.464 370.08 432.624 370.443 432.944 370.704C433.269 370.965 433.688 371.096 434.2 371.096ZM442.175 372.104C441.727 372.104 441.364 372.043 441.087 371.92C440.815 371.797 440.604 371.632 440.455 371.424C440.311 371.216 440.212 370.979 440.159 370.712C440.111 370.44 440.087 370.16 440.087 369.872V360.112H441.543V369.728C441.543 370.144 441.623 370.456 441.783 370.664C441.948 370.872 442.177 370.987 442.471 371.008L442.919 371.016V371.976C442.796 372.013 442.668 372.043 442.535 372.064C442.401 372.091 442.281 372.104 442.175 372.104ZM447.724 372V363.728H449.132V365.32C449.271 364.925 449.465 364.6 449.716 364.344C449.967 364.083 450.252 363.888 450.572 363.76C450.897 363.632 451.233 363.568 451.58 363.568C451.703 363.568 451.823 363.576 451.94 363.592C452.057 363.608 452.148 363.635 452.212 363.672V365.104C452.132 365.067 452.028 365.043 451.9 365.032C451.777 365.016 451.673 365.008 451.588 365.008C451.257 364.987 450.948 365.008 450.66 365.072C450.372 365.131 450.119 365.235 449.9 365.384C449.681 365.533 449.508 365.731 449.38 365.976C449.257 366.216 449.196 366.509 449.196 366.856V372H447.724ZM454.403 368.208C454.403 368.731 454.489 369.205 454.659 369.632C454.835 370.053 455.099 370.389 455.451 370.64C455.809 370.891 456.254 371.016 456.787 371.016C457.315 371.016 457.769 370.893 458.147 370.648C458.531 370.403 458.777 370.048 458.883 369.584H460.283C460.182 370.144 459.958 370.616 459.611 371C459.265 371.384 458.846 371.675 458.355 371.872C457.865 372.064 457.353 372.16 456.819 372.16C456.046 372.16 455.366 371.992 454.779 371.656C454.193 371.32 453.734 370.837 453.403 370.208C453.073 369.579 452.907 368.824 452.907 367.944C452.907 367.075 453.057 366.312 453.355 365.656C453.654 365 454.086 364.488 454.651 364.12C455.222 363.752 455.907 363.568 456.707 363.568C457.486 363.568 458.145 363.736 458.683 364.072C459.222 364.408 459.63 364.883 459.907 365.496C460.19 366.104 460.331 366.827 460.331 367.664V368.208H454.403ZM454.411 367.208H458.907C458.907 366.733 458.827 366.307 458.667 365.928C458.507 365.544 458.262 365.243 457.931 365.024C457.606 364.8 457.195 364.688 456.699 364.688C456.182 364.688 455.753 364.813 455.411 365.064C455.075 365.309 454.822 365.627 454.651 366.016C454.486 366.4 454.406 366.797 454.411 367.208ZM464.844 372.16C464.268 372.16 463.732 372.072 463.236 371.896C462.745 371.715 462.337 371.435 462.012 371.056C461.687 370.672 461.481 370.184 461.396 369.592H462.74C462.815 369.917 462.948 370.189 463.14 370.408C463.337 370.621 463.583 370.784 463.876 370.896C464.169 371.003 464.489 371.056 464.836 371.056C465.391 371.056 465.839 370.952 466.18 370.744C466.521 370.536 466.692 370.216 466.692 369.784C466.692 369.48 466.601 369.237 466.42 369.056C466.244 368.869 465.969 368.733 465.596 368.648L463.884 368.224C463.217 368.064 462.684 367.813 462.284 367.472C461.884 367.131 461.684 366.653 461.684 366.04C461.679 365.555 461.793 365.128 462.028 364.76C462.263 364.392 462.615 364.101 463.084 363.888C463.553 363.675 464.137 363.568 464.836 363.568C465.732 363.568 466.455 363.771 467.004 364.176C467.559 364.576 467.847 365.165 467.868 365.944H466.564C466.511 365.549 466.332 365.237 466.028 365.008C465.729 364.773 465.327 364.656 464.82 364.656C464.292 364.656 463.863 364.763 463.532 364.976C463.201 365.184 463.036 365.509 463.036 365.952C463.036 366.245 463.153 366.477 463.388 366.648C463.623 366.813 463.969 366.952 464.428 367.064L466.084 367.48C466.468 367.581 466.783 367.715 467.028 367.88C467.279 368.045 467.476 368.229 467.62 368.432C467.769 368.635 467.873 368.848 467.932 369.072C467.996 369.291 468.028 369.501 468.028 369.704C468.028 370.211 467.9 370.648 467.644 371.016C467.393 371.379 467.031 371.661 466.556 371.864C466.087 372.061 465.516 372.16 464.844 372.16ZM469.687 374.576V363.728H471.159L471.175 365.168C471.271 364.997 471.394 364.819 471.543 364.632C471.698 364.445 471.885 364.272 472.103 364.112C472.327 363.952 472.586 363.821 472.879 363.72C473.178 363.619 473.517 363.568 473.895 363.568C474.557 363.568 475.138 363.72 475.639 364.024C476.146 364.323 476.541 364.781 476.823 365.4C477.106 366.019 477.247 366.803 477.247 367.752C477.247 368.701 477.109 369.504 476.831 370.16C476.559 370.811 476.165 371.307 475.647 371.648C475.13 371.989 474.506 372.16 473.775 372.16C473.407 372.16 473.079 372.107 472.791 372C472.509 371.899 472.261 371.765 472.047 371.6C471.839 371.429 471.663 371.248 471.519 371.056C471.381 370.864 471.266 370.68 471.175 370.504V374.576H469.687ZM473.495 371.008C474.178 371.008 474.735 370.749 475.167 370.232C475.599 369.709 475.815 368.899 475.815 367.8C475.815 366.856 475.618 366.107 475.223 365.552C474.829 364.997 474.253 364.72 473.495 364.72C472.727 364.72 472.146 365.005 471.751 365.576C471.357 366.141 471.159 366.883 471.159 367.8C471.159 368.392 471.247 368.931 471.423 369.416C471.599 369.901 471.861 370.288 472.207 370.576C472.554 370.864 472.983 371.008 473.495 371.008ZM482.067 372.16C481.305 372.16 480.638 371.997 480.067 371.672C479.497 371.341 479.054 370.859 478.739 370.224C478.425 369.589 478.267 368.813 478.267 367.896C478.267 367.032 478.414 366.275 478.707 365.624C479.001 364.973 479.43 364.469 479.995 364.112C480.566 363.749 481.257 363.568 482.067 363.568C482.83 363.568 483.491 363.736 484.051 364.072C484.617 364.403 485.054 364.891 485.363 365.536C485.673 366.181 485.827 366.968 485.827 367.896C485.827 368.739 485.683 369.48 485.395 370.12C485.113 370.76 484.691 371.261 484.131 371.624C483.577 371.981 482.889 372.16 482.067 372.16ZM482.067 370.992C482.563 370.992 482.982 370.867 483.323 370.616C483.665 370.365 483.923 370.005 484.099 369.536C484.275 369.067 484.363 368.509 484.363 367.864C484.363 367.272 484.286 366.741 484.131 366.272C483.977 365.797 483.729 365.421 483.387 365.144C483.051 364.867 482.611 364.728 482.067 364.728C481.566 364.728 481.142 364.853 480.795 365.104C480.449 365.349 480.185 365.707 480.003 366.176C479.827 366.645 479.739 367.208 479.739 367.864C479.739 368.445 479.819 368.973 479.979 369.448C480.139 369.923 480.39 370.299 480.731 370.576C481.073 370.853 481.518 370.992 482.067 370.992ZM487.356 372V363.728H488.788V364.928C488.921 364.699 489.105 364.483 489.34 364.28C489.58 364.077 489.868 363.915 490.204 363.792C490.545 363.669 490.937 363.608 491.38 363.608C491.902 363.608 492.382 363.712 492.82 363.92C493.262 364.128 493.614 364.453 493.876 364.896C494.142 365.333 494.276 365.899 494.276 366.592V372H492.796V366.736C492.796 366.091 492.625 365.613 492.284 365.304C491.948 364.989 491.51 364.832 490.972 364.832C490.598 364.832 490.249 364.893 489.924 365.016C489.598 365.133 489.334 365.315 489.132 365.56C488.929 365.8 488.828 366.104 488.828 366.472V372H487.356ZM498.993 372.16C498.417 372.16 497.881 372.072 497.385 371.896C496.895 371.715 496.487 371.435 496.161 371.056C495.836 370.672 495.631 370.184 495.545 369.592H496.889C496.964 369.917 497.097 370.189 497.289 370.408C497.487 370.621 497.732 370.784 498.025 370.896C498.319 371.003 498.639 371.056 498.985 371.056C499.54 371.056 499.988 370.952 500.329 370.744C500.671 370.536 500.841 370.216 500.841 369.784C500.841 369.48 500.751 369.237 500.569 369.056C500.393 368.869 500.119 368.733 499.745 368.648L498.033 368.224C497.367 368.064 496.833 367.813 496.433 367.472C496.033 367.131 495.833 366.653 495.833 366.04C495.828 365.555 495.943 365.128 496.177 364.76C496.412 364.392 496.764 364.101 497.233 363.888C497.703 363.675 498.287 363.568 498.985 363.568C499.881 363.568 500.604 363.771 501.153 364.176C501.708 364.576 501.996 365.165 502.017 365.944H500.713C500.66 365.549 500.481 365.237 500.177 365.008C499.879 364.773 499.476 364.656 498.969 364.656C498.441 364.656 498.012 364.763 497.681 364.976C497.351 365.184 497.185 365.509 497.185 365.952C497.185 366.245 497.303 366.477 497.537 366.648C497.772 366.813 498.119 366.952 498.577 367.064L500.233 367.48C500.617 367.581 500.932 367.715 501.177 367.88C501.428 368.045 501.625 368.229 501.769 368.432C501.919 368.635 502.023 368.848 502.081 369.072C502.145 369.291 502.177 369.501 502.177 369.704C502.177 370.211 502.049 370.648 501.793 371.016C501.543 371.379 501.18 371.661 500.705 371.864C500.236 372.061 499.665 372.16 498.993 372.16ZM504.893 368.208C504.893 368.731 504.978 369.205 505.149 369.632C505.325 370.053 505.589 370.389 505.941 370.64C506.298 370.891 506.743 371.016 507.277 371.016C507.805 371.016 508.258 370.893 508.637 370.648C509.021 370.403 509.266 370.048 509.373 369.584H510.773C510.671 370.144 510.447 370.616 510.101 371C509.754 371.384 509.335 371.675 508.845 371.872C508.354 372.064 507.842 372.16 507.309 372.16C506.535 372.16 505.855 371.992 505.269 371.656C504.682 371.32 504.223 370.837 503.893 370.208C503.562 369.579 503.397 368.824 503.397 367.944C503.397 367.075 503.546 366.312 503.845 365.656C504.143 365 504.575 364.488 505.141 364.12C505.711 363.752 506.397 363.568 507.197 363.568C507.975 363.568 508.634 363.736 509.173 364.072C509.711 364.408 510.119 364.883 510.397 365.496C510.679 366.104 510.821 366.827 510.821 367.664V368.208H504.893ZM504.901 367.208H509.397C509.397 366.733 509.317 366.307 509.157 365.928C508.997 365.544 508.751 365.243 508.421 365.024C508.095 364.8 507.685 364.688 507.189 364.688C506.671 364.688 506.242 364.813 505.901 365.064C505.565 365.309 505.311 365.627 505.141 366.016C504.975 366.4 504.895 366.797 504.901 367.208Z" fill="white"/> +</g> +</g> +<defs> +<linearGradient id="paint0_linear_335_10946" x1="538.77" y1="164.8" x2="750.289" y2="174.569" gradientUnits="userSpaceOnUse"> +<stop stop-color="#B3D7FF"/> +<stop offset="1" stop-color="#0A84FF"/> +</linearGradient> +<linearGradient id="paint1_linear_335_10946" x1="473.207" y1="271.05" x2="227.182" y2="254.036" gradientUnits="userSpaceOnUse"> +<stop stop-color="#B3D7FF"/> +<stop offset="1" stop-color="#0A84FF"/> +</linearGradient> +<clipPath id="clip0_335_10946"> +<rect width="837" height="433" fill="white" transform="translate(0.5)"/> +</clipPath> +<clipPath id="clip1_335_10946"> +<rect width="24" height="24" fill="white" transform="translate(214.5 193)"/> +</clipPath> +<clipPath id="clip2_335_10946"> +<rect width="14" height="14" fill="white" transform="translate(419 198)"/> +</clipPath> +<clipPath id="clip3_335_10946"> +<rect width="20" height="20" fill="white" transform="translate(517 195)"/> +</clipPath> +<clipPath id="clip4_335_10946"> +<rect width="14" height="14" fill="white" transform="translate(469.5 146)"/> +</clipPath> +<clipPath id="clip5_335_10946"> +<rect width="20" height="20" fill="white" transform="translate(503.865 159)"/> +</clipPath> +<clipPath id="clip6_335_10946"> +<rect width="108" height="22" fill="white" transform="translate(426 38)"/> +</clipPath> +<clipPath id="clip7_335_10946"> +<rect width="174" height="22" fill="white" transform="translate(338 355)"/> +</clipPath> +</defs> +</svg> + diff --git a/docs/static/img/tool_chain.svg b/docs/static/img/tool_chain.svg new file mode 100644 index 0000000000000..35dc814e8040b --- /dev/null +++ b/docs/static/img/tool_chain.svg @@ -0,0 +1,306 @@ +<svg width="1016" height="433" viewBox="0 0 1016 433" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g clip-path="url(#clip0_336_13394)"> +<rect width="1016" height="433" rx="12" fill="#0F172A"/> +<rect x="24" y="188" width="62" height="34" rx="17" fill="#2B354E"/> +<path d="M37.528 199.432H39.008V211H37.528V199.432ZM41.3473 211V202.728H42.7793V203.928C42.9126 203.699 43.0966 203.483 43.3313 203.28C43.5713 203.077 43.8593 202.915 44.1953 202.792C44.5366 202.669 44.9286 202.608 45.3713 202.608C45.8939 202.608 46.3739 202.712 46.8113 202.92C47.2539 203.128 47.6059 203.453 47.8673 203.896C48.1339 204.333 48.2673 204.899 48.2673 205.592V211H46.7873V205.736C46.7873 205.091 46.6166 204.613 46.2753 204.304C45.9393 203.989 45.5019 203.832 44.9633 203.832C44.5899 203.832 44.2406 203.893 43.9153 204.016C43.5899 204.133 43.3259 204.315 43.1233 204.56C42.9206 204.8 42.8193 205.104 42.8193 205.472V211H41.3473ZM50.1736 213.576V202.728H51.6456L51.6616 204.168C51.7576 203.997 51.8803 203.819 52.0296 203.632C52.1843 203.445 52.371 203.272 52.5896 203.112C52.8136 202.952 53.0723 202.821 53.3656 202.72C53.6643 202.619 54.003 202.568 54.3816 202.568C55.043 202.568 55.6243 202.72 56.1256 203.024C56.6323 203.323 57.027 203.781 57.3096 204.4C57.5923 205.019 57.7336 205.803 57.7336 206.752C57.7336 207.701 57.595 208.504 57.3176 209.16C57.0456 209.811 56.651 210.307 56.1336 210.648C55.6163 210.989 54.9923 211.16 54.2616 211.16C53.8936 211.16 53.5656 211.107 53.2776 211C52.995 210.899 52.747 210.765 52.5336 210.6C52.3256 210.429 52.1496 210.248 52.0056 210.056C51.867 209.864 51.7523 209.68 51.6616 209.504V213.576H50.1736ZM53.9816 210.008C54.6643 210.008 55.2216 209.749 55.6536 209.232C56.0856 208.709 56.3016 207.899 56.3016 206.8C56.3016 205.856 56.1043 205.107 55.7096 204.552C55.315 203.997 54.739 203.72 53.9816 203.72C53.2136 203.72 52.6323 204.005 52.2376 204.576C51.843 205.141 51.6456 205.883 51.6456 206.8C51.6456 207.392 51.7336 207.931 51.9096 208.416C52.0856 208.901 52.347 209.288 52.6936 209.576C53.0403 209.864 53.4696 210.008 53.9816 210.008ZM62.1806 211.16C61.7486 211.155 61.3406 211.093 60.9566 210.976C60.578 210.853 60.242 210.675 59.9486 210.44C59.6553 210.205 59.4233 209.915 59.2526 209.568C59.0873 209.216 59.0046 208.808 59.0046 208.344V202.728H60.4846V208.192C60.4846 208.731 60.65 209.168 60.9806 209.504C61.3166 209.84 61.8206 210.008 62.4926 210.008C63.1006 210.008 63.5886 209.851 63.9566 209.536C64.33 209.216 64.5166 208.741 64.5166 208.112V202.728H65.9886V211H64.8126L64.6606 209.416C64.5593 209.832 64.386 210.168 64.1406 210.424C63.9006 210.68 63.61 210.869 63.2686 210.992C62.9326 211.109 62.57 211.165 62.1806 211.16ZM72.2009 203.808H70.3369V208.952C70.3369 209.245 70.3662 209.461 70.4249 209.6C70.4889 209.733 70.5929 209.819 70.7369 209.856C70.8862 209.893 71.0889 209.912 71.3449 209.912H72.2489V210.904C72.1475 210.941 71.9929 210.973 71.7849 211C71.5822 211.027 71.3289 211.04 71.0249 211.04C70.4542 211.04 70.0115 210.963 69.6969 210.808C69.3822 210.653 69.1609 210.424 69.0329 210.12C68.9102 209.816 68.8489 209.443 68.8489 209V203.808H67.5049V202.728H68.8889L69.2489 200.32H70.3369V202.72H72.2009V203.808Z" fill="white"/> +<path d="M117 206L112 203.113L112 208.887L117 206ZM102 206.5L112.5 206.5L112.5 205.5L102 205.5L102 206.5Z" fill="white"/> +<rect x="133" y="180" width="50" height="50" rx="12" fill="#FF8800" fill-opacity="0.9"/> +<rect x="133.5" y="180.5" width="49" height="49" rx="11.5" stroke="#FF8800" stroke-opacity="0.1"/> +<g clip-path="url(#clip1_336_13394)"> +<path d="M152.181 194.215C153.18 193.437 154.485 193.007 155.783 193.007C156.559 193.007 157.187 193.274 157.663 193.697C157.791 193.811 157.905 193.935 158.007 194.062C158.109 193.935 158.223 193.811 158.352 193.697C158.828 193.274 159.456 193.007 160.232 193.007C161.529 193.007 162.836 193.437 163.832 194.215C164.61 194.818 165.218 195.652 165.457 196.659C165.961 196.743 166.409 197 166.771 197.339C167.347 197.881 167.759 198.663 168.017 199.468C168.279 200.285 168.41 201.203 168.371 202.085C168.35 202.537 168.284 202.997 168.158 203.438L168.236 203.474C168.68 203.683 169.04 204.01 169.31 204.446C169.82 205.268 170 206.451 170 207.959C170 209.694 169.337 210.871 168.485 211.602C168.041 211.982 167.521 212.264 166.961 212.43C166.757 213.367 166.339 214.245 165.739 214.994C164.871 216.079 163.523 216.996 161.711 216.996C160.259 216.996 159.115 216.192 158.381 215.424C158.25 215.286 158.126 215.143 158.009 214.995C157.89 215.143 157.766 215.286 157.635 215.422C156.901 216.193 155.757 216.996 154.305 216.996C152.492 216.996 151.143 216.079 150.277 214.994C149.676 214.245 149.257 213.368 149.053 212.43C148.492 212.264 147.973 211.982 147.53 211.602C146.678 210.87 146.016 209.694 146.016 207.959C146.016 206.451 146.196 205.268 146.704 204.446C146.974 203.998 147.377 203.646 147.856 203.438C147.733 202.997 147.662 202.543 147.644 202.085C147.604 201.203 147.735 200.285 147.998 199.468C148.256 198.664 148.666 197.881 149.244 197.339C149.605 196.988 150.062 196.751 150.558 196.659C150.798 195.651 151.405 194.818 152.181 194.215ZM153.288 195.634C152.632 196.144 152.248 196.823 152.248 197.606C152.248 197.748 152.214 197.889 152.149 198.016C152.084 198.142 151.99 198.252 151.875 198.335C151.759 198.419 151.625 198.473 151.484 198.495C151.343 198.516 151.199 198.504 151.064 198.459C150.9 198.404 150.726 198.417 150.476 198.652C150.198 198.913 149.916 199.384 149.712 200.018C149.508 200.66 149.417 201.332 149.443 202.005C149.473 202.672 149.631 203.227 149.881 203.601C149.922 203.663 149.956 203.73 149.98 203.8H151.279C152.143 203.8 152.975 204.127 153.608 204.715C154.241 205.303 154.627 206.109 154.691 206.971C155.207 207.177 155.637 207.556 155.905 208.044C156.173 208.532 156.263 209.098 156.159 209.645C156.056 210.192 155.766 210.686 155.339 211.042C154.912 211.399 154.374 211.596 153.817 211.6C153.26 211.603 152.72 211.414 152.288 211.063C151.856 210.712 151.559 210.222 151.449 209.676C151.338 209.131 151.421 208.564 151.682 208.072C151.944 207.581 152.368 207.196 152.882 206.983C152.825 206.598 152.632 206.247 152.338 205.993C152.043 205.74 151.667 205.6 151.279 205.6H148.129C147.949 206.018 147.816 206.739 147.816 207.959C147.816 209.185 148.261 209.857 148.702 210.236C149.186 210.65 149.718 210.758 149.871 210.758C150.11 210.758 150.339 210.853 150.508 211.022C150.676 211.19 150.771 211.419 150.771 211.658C150.771 212.162 151.05 213.078 151.683 213.87C152.293 214.634 153.162 215.196 154.305 215.196C155.07 215.196 155.773 214.766 156.333 214.18C156.605 213.895 156.816 213.598 156.955 213.358C157.008 213.269 157.054 213.175 157.093 213.079L157.1 213.062V201.7H156.025C155.816 202.217 155.434 202.644 154.945 202.91C154.455 203.175 153.888 203.262 153.342 203.156C152.795 203.05 152.302 202.756 151.948 202.327C151.594 201.897 151.4 201.357 151.4 200.8C151.4 200.243 151.594 199.704 151.948 199.274C152.302 198.844 152.795 198.551 153.342 198.444C153.888 198.338 154.455 198.425 154.945 198.691C155.434 198.956 155.816 199.384 156.025 199.9H157.1V196.855L157.097 196.786C157.076 196.417 157.003 196.052 156.881 195.703C156.773 195.415 156.632 195.19 156.467 195.043C156.319 194.911 156.115 194.807 155.783 194.807C154.863 194.807 153.953 195.117 153.287 195.635M158.916 211V213.062L158.922 213.079C158.946 213.142 158.991 213.237 159.061 213.358C159.199 213.598 159.41 213.895 159.683 214.18C160.242 214.766 160.946 215.196 161.711 215.196C162.853 215.196 163.722 214.634 164.333 213.87C164.965 213.078 165.245 212.161 165.245 211.658C165.245 211.419 165.339 211.19 165.508 211.022C165.677 210.853 165.906 210.758 166.145 210.758C166.298 210.758 166.829 210.65 167.313 210.236C167.754 209.857 168.199 209.185 168.199 207.959C168.199 206.51 168.009 205.763 167.78 205.395C167.709 205.268 167.6 205.165 167.468 205.101C167.347 205.043 167.165 205 166.883 205C166.72 205 166.56 204.956 166.42 204.872C166.28 204.789 166.166 204.669 166.089 204.525C166.012 204.381 165.976 204.22 165.984 204.057C165.992 203.894 166.043 203.737 166.134 203.601C166.383 203.227 166.542 202.672 166.573 202.005C166.599 201.332 166.507 200.66 166.303 200.018C166.099 199.384 165.817 198.914 165.54 198.652C165.289 198.417 165.115 198.404 164.952 198.459C164.817 198.504 164.672 198.517 164.531 198.495C164.39 198.474 164.256 198.419 164.141 198.336C164.025 198.253 163.93 198.143 163.865 198.016C163.8 197.889 163.766 197.748 163.766 197.606C163.766 196.823 163.382 196.144 162.727 195.634C162.063 195.117 161.151 194.806 160.231 194.806C159.9 194.806 159.697 194.911 159.548 195.041C159.36 195.226 159.219 195.452 159.135 195.701C159.001 196.071 158.927 196.461 158.916 196.855V209.2H159.679C160.109 209.2 160.521 209.03 160.824 208.726C161.128 208.422 161.299 208.01 161.299 207.58V205.426C160.783 205.217 160.355 204.835 160.089 204.346C159.824 203.856 159.737 203.29 159.843 202.743C159.95 202.196 160.243 201.704 160.673 201.349C161.102 200.995 161.642 200.801 162.199 200.801C162.756 200.801 163.296 200.995 163.725 201.349C164.155 201.704 164.448 202.196 164.555 202.743C164.661 203.29 164.574 203.856 164.309 204.346C164.043 204.835 163.615 205.217 163.099 205.426V207.58C163.099 208.487 162.739 209.357 162.097 209.999C161.456 210.64 160.586 211 159.679 211H158.916ZM153.799 200.2C153.64 200.2 153.487 200.263 153.375 200.376C153.262 200.488 153.199 200.641 153.199 200.8C153.199 200.959 153.262 201.112 153.375 201.224C153.487 201.337 153.64 201.4 153.799 201.4C153.958 201.4 154.111 201.337 154.223 201.224C154.336 201.112 154.399 200.959 154.399 200.8C154.399 200.641 154.336 200.488 154.223 200.376C154.111 200.263 153.958 200.2 153.799 200.2ZM153.199 209.2C153.199 209.359 153.262 209.512 153.375 209.625C153.487 209.737 153.64 209.8 153.799 209.8C153.958 209.8 154.111 209.737 154.223 209.625C154.336 209.512 154.399 209.359 154.399 209.2C154.399 209.041 154.336 208.889 154.223 208.776C154.111 208.664 153.958 208.6 153.799 208.6C153.64 208.6 153.487 208.664 153.375 208.776C153.262 208.889 153.199 209.041 153.199 209.2ZM161.599 203.2C161.599 203.359 161.662 203.512 161.775 203.625C161.887 203.737 162.04 203.8 162.199 203.8C162.358 203.8 162.511 203.737 162.623 203.625C162.736 203.512 162.799 203.359 162.799 203.2C162.799 203.041 162.736 202.888 162.623 202.776C162.511 202.663 162.358 202.6 162.199 202.6C162.04 202.6 161.887 202.663 161.775 202.776C161.662 202.888 161.599 203.041 161.599 203.2Z" fill="white"/> +</g> +<path d="M146.282 240.878H147.801V249.684H152.477V251H146.275L146.282 240.878ZM154.862 240.878H156.381V249.684H161.057V251H154.855L154.862 240.878ZM163.435 251V240.878H165.549L168.398 248.928L171.289 240.878H173.389V251H171.856V242.936L169.056 251H167.747L164.961 242.999V251H163.435Z" fill="white"/> +<path d="M214 206L209 203.113L209 208.887L214 206ZM199 206.5L209.5 206.5L209.5 205.5L199 205.5L199 206.5Z" fill="white"/> +<rect x="230" y="56.5" width="296" height="320" rx="12" fill="#9AB4B8" fill-opacity="0.1"/> +<rect x="262" y="180" width="50" height="50" rx="12" fill="#64748B" fill-opacity="0.4"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M277.143 199.286C277.143 197.471 278.614 196 280.429 196C280.981 196 281.429 196.448 281.429 197C281.429 197.552 280.981 198 280.429 198C279.718 198 279.143 198.575 279.143 199.286V203.857C279.143 204.122 279.038 204.377 278.85 204.564L278.414 205L278.85 205.436C279.038 205.623 279.143 205.878 279.143 206.143V210.714C279.143 211.425 279.718 212 280.429 212C280.981 212 281.429 212.448 281.429 213C281.429 213.552 280.981 214 280.429 214C278.614 214 277.143 212.529 277.143 210.714V206.557L276.293 205.707C276.105 205.52 276 205.265 276 205C276 204.735 276.105 204.48 276.293 204.293L277.143 203.443V199.286ZM292.571 197C292.571 196.448 293.019 196 293.571 196C295.386 196 296.857 197.471 296.857 199.286V203.443L297.707 204.293C298.097 204.683 298.097 205.317 297.707 205.707L296.857 206.557V210.714C296.857 212.53 295.385 214 293.571 214C293.019 214 292.571 213.552 292.571 213C292.571 212.448 293.019 212 293.571 212C294.281 212 294.857 211.424 294.857 210.714V206.143C294.857 205.878 294.962 205.623 295.15 205.436L295.586 205L295.15 204.564C294.962 204.377 294.857 204.122 294.857 203.857V199.286C294.857 198.575 294.282 198 293.571 198C293.019 198 292.571 197.552 292.571 197ZM283.293 201.293C283.683 200.902 284.317 200.902 284.707 201.293L287 203.586L289.293 201.293C289.683 200.902 290.317 200.902 290.707 201.293C291.098 201.683 291.098 202.317 290.707 202.707L288.414 205L290.707 207.293C291.098 207.683 291.098 208.317 290.707 208.707C290.317 209.098 289.683 209.098 289.293 208.707L287 206.414L284.707 208.707C284.317 209.098 283.683 209.098 283.293 208.707C282.902 208.317 282.902 207.683 283.293 207.293L285.586 205L283.293 202.707C282.902 202.317 282.902 201.683 283.293 201.293Z" fill="white"/> +<path d="M259.386 251V240.878H262.893C263.617 240.878 264.263 241.002 264.832 241.249C265.406 241.496 265.857 241.86 266.183 242.341C266.515 242.822 266.68 243.412 266.68 244.112C266.68 244.784 266.529 245.358 266.225 245.834C265.927 246.305 265.507 246.667 264.965 246.919C264.429 247.166 263.806 247.29 263.096 247.29H260.912V251H259.386ZM260.905 246.065H263.054C263.698 246.065 264.223 245.881 264.629 245.512C265.035 245.139 265.238 244.644 265.238 244.028C265.238 243.403 265.028 242.924 264.608 242.593C264.188 242.257 263.652 242.089 262.998 242.089H260.905V246.065ZM267.238 251L270.892 240.878H272.544L276.191 251H274.679L273.727 248.347H269.73L268.757 251H267.238ZM270.087 246.989H273.342L271.725 242.348L270.087 246.989ZM285.908 251H284.333L282.541 246.765H280V251H278.502V240.878H282.52C283.271 240.878 283.894 240.995 284.389 241.228C284.883 241.457 285.252 241.783 285.495 242.208C285.737 242.633 285.859 243.134 285.859 243.713C285.859 244.259 285.765 244.714 285.579 245.078C285.397 245.442 285.159 245.738 284.865 245.967C284.575 246.196 284.27 246.375 283.948 246.506L285.908 251ZM282.114 245.554C282.818 245.554 283.364 245.402 283.752 245.099C284.139 244.791 284.333 244.352 284.333 243.783C284.333 243.223 284.155 242.798 283.801 242.509C283.451 242.22 282.975 242.075 282.373 242.075H280V245.554H282.114ZM291.82 251.14C291.339 251.14 290.87 251.079 290.413 250.958C289.956 250.837 289.54 250.657 289.167 250.419C288.794 250.176 288.483 249.875 288.236 249.516C287.993 249.157 287.842 248.737 287.781 248.256H289.342C289.421 248.62 289.578 248.926 289.811 249.173C290.049 249.42 290.341 249.607 290.686 249.733C291.031 249.859 291.407 249.922 291.813 249.922C292.261 249.922 292.662 249.861 293.017 249.74C293.376 249.614 293.661 249.437 293.871 249.208C294.081 248.975 294.186 248.695 294.186 248.368C294.186 248.074 294.114 247.824 293.969 247.619C293.829 247.414 293.628 247.243 293.367 247.108C293.11 246.968 292.8 246.858 292.436 246.779L290.518 246.345C289.767 246.186 289.174 245.892 288.74 245.463C288.311 245.029 288.094 244.455 288.089 243.741C288.084 243.144 288.241 242.621 288.558 242.173C288.88 241.725 289.319 241.377 289.874 241.13C290.429 240.878 291.062 240.752 291.771 240.752C292.597 240.752 293.29 240.89 293.85 241.165C294.415 241.44 294.842 241.804 295.131 242.257C295.42 242.705 295.567 243.193 295.572 243.72H294.039C294.002 243.291 293.876 242.95 293.661 242.698C293.446 242.446 293.176 242.266 292.849 242.159C292.527 242.047 292.172 241.991 291.785 241.991C291.505 241.991 291.237 242.026 290.98 242.096C290.723 242.166 290.497 242.269 290.301 242.404C290.105 242.539 289.949 242.707 289.832 242.908C289.72 243.104 289.664 243.33 289.664 243.587C289.664 243.932 289.781 244.212 290.014 244.427C290.247 244.642 290.677 244.826 291.302 244.98L293.178 245.407C293.715 245.519 294.149 245.685 294.48 245.904C294.811 246.119 295.066 246.364 295.243 246.639C295.42 246.914 295.542 247.201 295.607 247.5C295.672 247.794 295.705 248.076 295.705 248.347C295.705 248.87 295.549 249.343 295.236 249.768C294.923 250.188 294.478 250.522 293.899 250.769C293.32 251.016 292.627 251.14 291.82 251.14ZM298.246 251V240.878H304.784L304.777 242.18H299.751V245.253H304.238V246.534H299.744V249.677L304.903 249.684V251H298.246ZM315.039 251H313.464L311.672 246.765H309.131V251H307.633V240.878H311.651C312.402 240.878 313.025 240.995 313.52 241.228C314.014 241.457 314.383 241.783 314.626 242.208C314.868 242.633 314.99 243.134 314.99 243.713C314.99 244.259 314.896 244.714 314.71 245.078C314.528 245.442 314.29 245.738 313.996 245.967C313.706 246.196 313.401 246.375 313.079 246.506L315.039 251ZM311.245 245.554C311.949 245.554 312.495 245.402 312.883 245.099C313.27 244.791 313.464 244.352 313.464 243.783C313.464 243.223 313.286 242.798 312.932 242.509C312.582 242.22 312.106 242.075 311.504 242.075H309.131V245.554H311.245Z" fill="white"/> +<rect x="340" y="132" width="158" height="169" rx="12" fill="#394659"/> +<path d="M366.473 158.146C366.237 158.302 365.951 158.43 365.615 158.53C365.279 158.63 364.915 158.68 364.523 158.68C363.747 158.68 363.163 158.48 362.771 158.08C362.379 157.676 362.183 157.14 362.183 156.472V153.268H360.803V152.146H362.183V150.748L363.767 150.556V152.146H365.867L365.705 153.268H363.767V156.466C363.767 156.794 363.847 157.034 364.007 157.186C364.167 157.338 364.425 157.414 364.781 157.414C365.009 157.414 365.217 157.388 365.405 157.336C365.597 157.28 365.771 157.21 365.927 157.126L366.473 158.146ZM370.825 151.966C371.457 151.966 371.993 152.104 372.433 152.38C372.873 152.656 373.207 153.046 373.435 153.55C373.667 154.05 373.783 154.636 373.783 155.308C373.783 155.996 373.667 156.592 373.435 157.096C373.203 157.6 372.867 157.99 372.427 158.266C371.987 158.542 371.451 158.68 370.819 158.68C370.191 158.68 369.655 158.546 369.211 158.278C368.771 158.006 368.435 157.62 368.203 157.12C367.971 156.616 367.855 156.016 367.855 155.32C367.855 154.656 367.971 154.072 368.203 153.568C368.435 153.064 368.773 152.672 369.217 152.392C369.661 152.108 370.197 151.966 370.825 151.966ZM370.825 153.16C370.381 153.16 370.049 153.334 369.829 153.682C369.613 154.03 369.505 154.576 369.505 155.32C369.505 156.072 369.613 156.622 369.829 156.97C370.045 157.314 370.375 157.486 370.819 157.486C371.263 157.486 371.593 157.314 371.809 156.97C372.025 156.622 372.133 156.068 372.133 155.308C372.133 154.568 372.025 154.026 371.809 153.682C371.593 153.334 371.265 153.16 370.825 153.16ZM378.02 151.966C378.652 151.966 379.188 152.104 379.628 152.38C380.068 152.656 380.402 153.046 380.63 153.55C380.862 154.05 380.978 154.636 380.978 155.308C380.978 155.996 380.862 156.592 380.63 157.096C380.398 157.6 380.062 157.99 379.622 158.266C379.182 158.542 378.646 158.68 378.014 158.68C377.386 158.68 376.85 158.546 376.406 158.278C375.966 158.006 375.63 157.62 375.398 157.12C375.166 156.616 375.05 156.016 375.05 155.32C375.05 154.656 375.166 154.072 375.398 153.568C375.63 153.064 375.968 152.672 376.412 152.392C376.856 152.108 377.392 151.966 378.02 151.966ZM378.02 153.16C377.576 153.16 377.244 153.334 377.024 153.682C376.808 154.03 376.7 154.576 376.7 155.32C376.7 156.072 376.808 156.622 377.024 156.97C377.24 157.314 377.57 157.486 378.014 157.486C378.458 157.486 378.788 157.314 379.004 156.97C379.22 156.622 379.328 156.068 379.328 155.308C379.328 154.568 379.22 154.026 379.004 153.682C378.788 153.334 378.46 153.16 378.02 153.16ZM385.599 149.608V156.652C385.599 156.928 385.679 157.124 385.839 157.24C386.003 157.356 386.223 157.414 386.499 157.414C386.671 157.414 386.837 157.396 386.997 157.36C387.157 157.32 387.309 157.272 387.453 157.216L387.843 158.296C387.643 158.396 387.399 158.484 387.111 158.56C386.827 158.64 386.497 158.68 386.121 158.68C385.429 158.68 384.905 158.482 384.549 158.086C384.193 157.69 384.015 157.156 384.015 156.484V150.73H382.113V149.608H385.599ZM389.405 160.714V159.436H395.405V160.714H389.405ZM396.966 158.5V152.146H398.346L398.46 152.932C398.732 152.612 399.038 152.372 399.378 152.212C399.718 152.048 400.084 151.966 400.476 151.966C401.048 151.966 401.484 152.132 401.784 152.464C402.084 152.792 402.234 153.254 402.234 153.85V158.5H400.65V154.468C400.65 154.148 400.63 153.892 400.59 153.7C400.55 153.504 400.474 153.364 400.362 153.28C400.25 153.192 400.088 153.148 399.876 153.148C399.704 153.148 399.538 153.186 399.378 153.262C399.222 153.338 399.074 153.44 398.934 153.568C398.798 153.696 398.67 153.838 398.55 153.994V158.5H396.966ZM409.231 156.826C409.231 157.07 409.267 157.248 409.339 157.36C409.411 157.468 409.527 157.55 409.687 157.606L409.345 158.668C409.005 158.632 408.715 158.548 408.475 158.416C408.239 158.28 408.057 158.078 407.929 157.81C407.689 158.102 407.385 158.32 407.017 158.464C406.653 158.608 406.271 158.68 405.871 158.68C405.223 158.68 404.709 158.496 404.329 158.128C403.949 157.76 403.759 157.282 403.759 156.694C403.759 156.018 404.023 155.498 404.551 155.134C405.083 154.766 405.835 154.582 406.807 154.582H407.695V154.24C407.695 153.864 407.579 153.592 407.347 153.424C407.115 153.252 406.781 153.166 406.345 153.166C406.141 153.166 405.885 153.194 405.577 153.25C405.273 153.302 404.959 153.384 404.635 153.496L404.257 152.41C404.661 152.258 405.067 152.146 405.475 152.074C405.887 152.002 406.261 151.966 406.597 151.966C407.489 151.966 408.151 152.156 408.583 152.536C409.015 152.912 409.231 153.446 409.231 154.138V156.826ZM406.387 157.534C406.627 157.534 406.869 157.466 407.113 157.33C407.361 157.194 407.555 157.004 407.695 156.76V155.524H407.071C406.483 155.524 406.057 155.618 405.793 155.806C405.533 155.994 405.403 156.26 405.403 156.604C405.403 156.9 405.487 157.13 405.655 157.294C405.823 157.454 406.067 157.534 406.387 157.534ZM415.875 151.966C416.147 151.966 416.379 152.024 416.571 152.14C416.763 152.252 416.911 152.442 417.015 152.71C417.119 152.974 417.171 153.334 417.171 153.79V158.5H415.791V153.988C415.791 153.7 415.767 153.496 415.719 153.376C415.675 153.256 415.573 153.196 415.413 153.196C415.281 153.196 415.147 153.236 415.011 153.316C414.879 153.392 414.741 153.53 414.597 153.73V158.5H413.403V153.988C413.403 153.7 413.379 153.496 413.331 153.376C413.287 153.256 413.185 153.196 413.025 153.196C412.889 153.196 412.755 153.236 412.623 153.316C412.491 153.392 412.353 153.53 412.209 153.73V158.5H410.811V152.146H411.993L412.095 152.806C412.283 152.542 412.481 152.336 412.689 152.188C412.897 152.04 413.147 151.966 413.439 151.966C413.679 151.966 413.891 152.026 414.075 152.146C414.263 152.262 414.397 152.462 414.477 152.746C414.661 152.514 414.867 152.326 415.095 152.182C415.323 152.038 415.583 151.966 415.875 151.966ZM419.902 155.806C419.934 156.202 420.024 156.524 420.172 156.772C420.324 157.02 420.518 157.202 420.754 157.318C420.99 157.434 421.252 157.492 421.54 157.492C421.844 157.492 422.13 157.444 422.398 157.348C422.666 157.252 422.93 157.118 423.19 156.946L423.85 157.852C423.554 158.1 423.2 158.3 422.788 158.452C422.38 158.604 421.93 158.68 421.438 158.68C420.75 158.68 420.17 158.538 419.698 158.254C419.23 157.97 418.876 157.576 418.636 157.072C418.396 156.568 418.276 155.988 418.276 155.332C418.276 154.7 418.394 154.13 418.63 153.622C418.866 153.114 419.206 152.712 419.65 152.416C420.094 152.116 420.63 151.966 421.258 151.966C421.842 151.966 422.346 152.094 422.77 152.35C423.194 152.606 423.522 152.974 423.754 153.454C423.986 153.93 424.102 154.504 424.102 155.176C424.102 155.284 424.098 155.394 424.09 155.506C424.086 155.618 424.08 155.718 424.072 155.806H419.902ZM421.264 153.082C420.88 153.082 420.564 153.22 420.316 153.496C420.072 153.768 419.928 154.2 419.884 154.792H422.56C422.552 154.256 422.44 153.838 422.224 153.538C422.012 153.234 421.692 153.082 421.264 153.082ZM427.217 157.516C427.217 157.204 427.329 156.936 427.553 156.712C427.777 156.484 428.051 156.37 428.375 156.37C428.703 156.37 428.977 156.484 429.197 156.712C429.421 156.936 429.533 157.204 429.533 157.516C429.533 157.836 429.421 158.11 429.197 158.338C428.977 158.566 428.703 158.68 428.375 158.68C428.051 158.68 427.777 158.566 427.553 158.338C427.329 158.11 427.217 157.836 427.217 157.516ZM427.217 153.304C427.217 153.092 427.269 152.9 427.373 152.728C427.477 152.552 427.617 152.412 427.793 152.308C427.969 152.204 428.163 152.152 428.375 152.152C428.703 152.152 428.977 152.266 429.197 152.494C429.421 152.722 429.533 152.992 429.533 153.304C429.533 153.62 429.421 153.892 429.197 154.12C428.977 154.348 428.703 154.462 428.375 154.462C428.051 154.462 427.777 154.348 427.553 154.12C427.329 153.892 427.217 153.62 427.217 153.304Z" fill="white"/> +<path d="M440 148C440 145.791 441.791 144 444 144H474C476.209 144 478 145.791 478 148V161C478 163.209 476.209 165 474 165H444C441.791 165 440 163.209 440 161V148Z" fill="#526176" fill-opacity="0.8"/> +<path d="M457.741 157.576C457.741 157.38 457.787 157.204 457.879 157.048C457.971 156.888 458.097 156.762 458.257 156.67C458.413 156.574 458.591 156.526 458.791 156.526C458.995 156.526 459.177 156.574 459.337 156.67C459.497 156.762 459.623 156.888 459.715 157.048C459.807 157.204 459.853 157.38 459.853 157.576C459.853 157.772 459.807 157.952 459.715 158.116C459.623 158.276 459.497 158.404 459.337 158.5C459.177 158.592 458.995 158.638 458.791 158.638C458.591 158.638 458.413 158.592 458.257 158.5C458.097 158.404 457.971 158.276 457.879 158.116C457.787 157.952 457.741 157.772 457.741 157.576ZM462.541 157.576C462.541 157.38 462.587 157.204 462.679 157.048C462.771 156.888 462.895 156.762 463.051 156.67C463.211 156.574 463.391 156.526 463.591 156.526C463.795 156.526 463.977 156.574 464.137 156.67C464.297 156.762 464.423 156.888 464.515 157.048C464.607 157.204 464.653 157.38 464.653 157.576C464.653 157.772 464.607 157.952 464.515 158.116C464.423 158.276 464.297 158.404 464.137 158.5C463.977 158.592 463.795 158.638 463.591 158.638C463.391 158.638 463.211 158.592 463.051 158.5C462.895 158.404 462.771 158.276 462.679 158.116C462.587 157.952 462.541 157.772 462.541 157.576ZM452.941 157.576C452.941 157.38 452.987 157.204 453.079 157.048C453.171 156.888 453.297 156.762 453.457 156.67C453.613 156.574 453.791 156.526 453.991 156.526C454.195 156.526 454.377 156.574 454.537 156.67C454.697 156.762 454.823 156.888 454.915 157.048C455.007 157.204 455.053 157.38 455.053 157.576C455.053 157.772 455.007 157.952 454.915 158.116C454.823 158.276 454.697 158.404 454.537 158.5C454.377 158.592 454.195 158.638 453.991 158.638C453.791 158.638 453.613 158.592 453.457 158.5C453.297 158.404 453.171 158.276 453.079 158.116C452.987 157.952 452.941 157.772 452.941 157.576Z" fill="white"/> +<rect x="352.5" y="173.5" width="133" height="115" rx="5.5" fill="#65758D" fill-opacity="0.8"/> +<rect x="352.5" y="173.5" width="133" height="115" rx="5.5" stroke="#334155"/> +<path d="M374.383 195.164C374.151 195.312 373.873 195.432 373.549 195.524C373.225 195.616 372.887 195.662 372.535 195.662C371.799 195.662 371.239 195.472 370.855 195.092C370.475 194.708 370.285 194.204 370.285 193.58V190.142H368.875V189.164H370.285V187.748L371.629 187.586V189.164H373.765L373.615 190.142H371.629V193.568C371.629 193.908 371.713 194.162 371.881 194.33C372.053 194.494 372.333 194.576 372.721 194.576C372.953 194.576 373.167 194.548 373.363 194.492C373.559 194.436 373.737 194.364 373.897 194.276L374.383 195.164ZM378.825 188.996C379.445 188.996 379.967 189.134 380.391 189.41C380.815 189.686 381.135 190.072 381.351 190.568C381.571 191.064 381.681 191.648 381.681 192.32C381.681 192.988 381.569 193.574 381.345 194.078C381.125 194.578 380.801 194.968 380.373 195.248C379.949 195.524 379.431 195.662 378.819 195.662C378.207 195.662 377.687 195.528 377.259 195.26C376.835 194.988 376.511 194.602 376.287 194.102C376.067 193.602 375.957 193.012 375.957 192.332C375.957 191.672 376.067 191.092 376.287 190.592C376.511 190.092 376.837 189.702 377.265 189.422C377.693 189.138 378.213 188.996 378.825 188.996ZM378.825 190.04C378.341 190.04 377.977 190.226 377.733 190.598C377.489 190.966 377.367 191.544 377.367 192.332C377.367 193.116 377.487 193.694 377.727 194.066C377.971 194.438 378.335 194.624 378.819 194.624C379.303 194.624 379.665 194.438 379.905 194.066C380.149 193.694 380.271 193.112 380.271 192.32C380.271 191.54 380.151 190.966 379.911 190.598C379.671 190.226 379.309 190.04 378.825 190.04ZM386.02 188.996C386.64 188.996 387.162 189.134 387.586 189.41C388.01 189.686 388.33 190.072 388.546 190.568C388.766 191.064 388.876 191.648 388.876 192.32C388.876 192.988 388.764 193.574 388.54 194.078C388.32 194.578 387.996 194.968 387.568 195.248C387.144 195.524 386.626 195.662 386.014 195.662C385.402 195.662 384.882 195.528 384.454 195.26C384.03 194.988 383.706 194.602 383.482 194.102C383.262 193.602 383.152 193.012 383.152 192.332C383.152 191.672 383.262 191.092 383.482 190.592C383.706 190.092 384.032 189.702 384.46 189.422C384.888 189.138 385.408 188.996 386.02 188.996ZM386.02 190.04C385.536 190.04 385.172 190.226 384.928 190.598C384.684 190.966 384.562 191.544 384.562 192.332C384.562 193.116 384.682 193.694 384.922 194.066C385.166 194.438 385.53 194.624 386.014 194.624C386.498 194.624 386.86 194.438 387.1 194.066C387.344 193.694 387.466 193.112 387.466 192.32C387.466 191.54 387.346 190.966 387.106 190.598C386.866 190.226 386.504 190.04 386.02 190.04ZM393.461 186.62V193.772C393.461 194.064 393.549 194.272 393.725 194.396C393.905 194.516 394.137 194.576 394.421 194.576C394.605 194.576 394.779 194.556 394.943 194.516C395.107 194.476 395.267 194.426 395.423 194.366L395.765 195.302C395.577 195.398 395.345 195.482 395.069 195.554C394.797 195.626 394.485 195.662 394.133 195.662C393.497 195.662 393.001 195.484 392.645 195.128C392.293 194.768 392.117 194.276 392.117 193.652V187.61H390.203V186.62H393.461ZM397.405 197.696V196.586H403.405V197.696H397.405ZM409.91 193.946C409.91 194.194 409.948 194.376 410.024 194.492C410.1 194.604 410.222 194.688 410.39 194.744L410.09 195.656C409.778 195.62 409.51 195.534 409.286 195.398C409.062 195.262 408.892 195.056 408.776 194.78C408.544 195.072 408.25 195.292 407.894 195.44C407.538 195.588 407.158 195.662 406.754 195.662C406.114 195.662 405.606 195.482 405.23 195.122C404.858 194.758 404.672 194.282 404.672 193.694C404.672 193.026 404.932 192.512 405.452 192.152C405.972 191.792 406.716 191.612 407.684 191.612H408.59V191.198C408.59 190.79 408.466 190.496 408.218 190.316C407.974 190.136 407.628 190.046 407.18 190.046C406.972 190.046 406.72 190.074 406.424 190.13C406.128 190.182 405.818 190.264 405.494 190.376L405.164 189.428C405.56 189.28 405.948 189.172 406.328 189.104C406.708 189.032 407.06 188.996 407.384 188.996C408.232 188.996 408.864 189.184 409.28 189.56C409.7 189.936 409.91 190.456 409.91 191.12V193.946ZM407.162 194.672C407.434 194.672 407.7 194.6 407.96 194.456C408.22 194.312 408.43 194.11 408.59 193.85V192.452H407.846C407.214 192.452 406.762 192.558 406.49 192.77C406.218 192.978 406.082 193.266 406.082 193.634C406.082 193.97 406.172 194.228 406.352 194.408C406.536 194.584 406.806 194.672 407.162 194.672ZM412.323 195.5V194.54H413.253V190.118H412.323V189.164H414.279L414.525 190.634C414.761 190.102 415.053 189.698 415.401 189.422C415.753 189.142 416.205 189.002 416.757 189.002C416.945 189.002 417.113 189.018 417.261 189.05C417.413 189.078 417.563 189.116 417.711 189.164L417.309 190.316C417.181 190.28 417.061 190.254 416.949 190.238C416.837 190.218 416.713 190.208 416.577 190.208C416.117 190.208 415.719 190.38 415.383 190.724C415.051 191.068 414.789 191.556 414.597 192.188V194.54H415.875V195.5H412.323ZM416.523 191.498V189.938L416.691 189.164H417.711L417.483 191.498H416.523ZM424.793 188.36L425.153 189.476C424.901 189.56 424.623 189.616 424.319 189.644C424.019 189.672 423.683 189.686 423.311 189.686C423.691 189.854 423.977 190.07 424.169 190.334C424.361 190.594 424.457 190.914 424.457 191.294C424.457 191.706 424.357 192.074 424.157 192.398C423.957 192.722 423.671 192.976 423.299 193.16C422.927 193.344 422.481 193.436 421.961 193.436C421.777 193.436 421.617 193.428 421.481 193.412C421.349 193.396 421.219 193.37 421.091 193.334C421.011 193.39 420.943 193.466 420.887 193.562C420.835 193.654 420.809 193.752 420.809 193.856C420.809 193.984 420.861 194.096 420.965 194.192C421.073 194.284 421.285 194.33 421.601 194.33H422.705C423.157 194.33 423.555 194.408 423.899 194.564C424.247 194.716 424.519 194.926 424.715 195.194C424.915 195.458 425.015 195.758 425.015 196.094C425.015 196.726 424.743 197.216 424.199 197.564C423.655 197.912 422.871 198.086 421.847 198.086C421.123 198.086 420.555 198.012 420.143 197.864C419.731 197.716 419.441 197.496 419.273 197.204C419.105 196.912 419.021 196.554 419.021 196.13H420.227C420.227 196.35 420.269 196.532 420.353 196.676C420.441 196.82 420.603 196.928 420.839 197C421.075 197.072 421.415 197.108 421.859 197.108C422.303 197.108 422.655 197.07 422.915 196.994C423.179 196.922 423.369 196.816 423.485 196.676C423.605 196.54 423.665 196.376 423.665 196.184C423.665 195.928 423.553 195.73 423.329 195.59C423.105 195.45 422.805 195.38 422.429 195.38H421.343C420.743 195.38 420.303 195.258 420.023 195.014C419.747 194.77 419.609 194.486 419.609 194.162C419.609 193.946 419.669 193.738 419.789 193.538C419.913 193.338 420.089 193.164 420.317 193.016C419.937 192.816 419.659 192.574 419.483 192.29C419.311 192.002 419.225 191.654 419.225 191.246C419.225 190.798 419.337 190.406 419.561 190.07C419.785 189.734 420.095 189.472 420.491 189.284C420.891 189.096 421.351 189.002 421.871 189.002C422.379 189.002 422.803 188.976 423.143 188.924C423.483 188.868 423.779 188.79 424.031 188.69C424.287 188.59 424.541 188.48 424.793 188.36ZM421.883 189.926C421.455 189.926 421.133 190.048 420.917 190.292C420.701 190.532 420.593 190.846 420.593 191.234C420.593 191.634 420.703 191.956 420.923 192.2C421.147 192.44 421.473 192.56 421.901 192.56C422.297 192.56 422.599 192.444 422.807 192.212C423.019 191.98 423.125 191.65 423.125 191.222C423.125 190.79 423.021 190.466 422.813 190.25C422.605 190.034 422.295 189.926 421.883 189.926ZM428.958 194.63C429.378 194.63 429.712 194.556 429.96 194.408C430.208 194.256 430.332 194.048 430.332 193.784C430.332 193.612 430.296 193.466 430.224 193.346C430.152 193.222 430.008 193.11 429.792 193.01C429.576 192.906 429.248 192.798 428.808 192.686C428.392 192.578 428.026 192.45 427.71 192.302C427.398 192.154 427.156 191.962 426.984 191.726C426.812 191.49 426.726 191.186 426.726 190.814C426.726 190.45 426.828 190.132 427.032 189.86C427.236 189.588 427.528 189.376 427.908 189.224C428.292 189.072 428.742 188.996 429.258 188.996C429.79 188.996 430.254 189.066 430.65 189.206C431.046 189.346 431.384 189.52 431.664 189.728L431.1 190.574C430.852 190.41 430.58 190.276 430.284 190.172C429.992 190.068 429.662 190.016 429.294 190.016C428.866 190.016 428.558 190.08 428.37 190.208C428.186 190.336 428.094 190.506 428.094 190.718C428.094 190.874 428.14 191.006 428.232 191.114C428.328 191.218 428.492 191.316 428.724 191.408C428.956 191.496 429.284 191.6 429.708 191.72C430.12 191.832 430.478 191.964 430.782 192.116C431.09 192.268 431.328 192.47 431.496 192.722C431.668 192.97 431.754 193.294 431.754 193.694C431.754 194.15 431.622 194.524 431.358 194.816C431.098 195.104 430.754 195.318 430.326 195.458C429.902 195.594 429.446 195.662 428.958 195.662C428.37 195.662 427.862 195.578 427.434 195.41C427.006 195.238 426.646 195.024 426.354 194.768L427.068 193.952C427.316 194.152 427.6 194.316 427.92 194.444C428.24 194.568 428.586 194.63 428.958 194.63ZM435.295 194.576C435.295 194.284 435.399 194.034 435.607 193.826C435.815 193.614 436.071 193.508 436.375 193.508C436.683 193.508 436.939 193.614 437.143 193.826C437.351 194.034 437.455 194.284 437.455 194.576C437.455 194.876 437.351 195.132 437.143 195.344C436.939 195.556 436.683 195.662 436.375 195.662C436.071 195.662 435.815 195.556 435.607 195.344C435.399 195.132 435.295 194.876 435.295 194.576ZM435.295 190.316C435.295 190.12 435.343 189.94 435.439 189.776C435.535 189.612 435.665 189.482 435.829 189.386C435.993 189.29 436.175 189.242 436.375 189.242C436.683 189.242 436.939 189.348 437.143 189.56C437.351 189.768 437.455 190.02 437.455 190.316C437.455 190.612 437.351 190.868 437.143 191.084C436.939 191.296 436.683 191.402 436.375 191.402C436.071 191.402 435.815 191.296 435.607 191.084C435.399 190.868 435.295 190.612 435.295 190.316Z" fill="white"/> +<path d="M400.422 222.946C400.422 223.194 400.46 223.376 400.536 223.492C400.612 223.604 400.734 223.688 400.902 223.744L400.602 224.656C400.29 224.62 400.022 224.534 399.798 224.398C399.574 224.262 399.404 224.056 399.288 223.78C399.056 224.072 398.762 224.292 398.406 224.44C398.05 224.588 397.67 224.662 397.266 224.662C396.626 224.662 396.118 224.482 395.742 224.122C395.37 223.758 395.184 223.282 395.184 222.694C395.184 222.026 395.444 221.512 395.964 221.152C396.484 220.792 397.228 220.612 398.196 220.612H399.102V220.198C399.102 219.79 398.978 219.496 398.73 219.316C398.486 219.136 398.14 219.046 397.692 219.046C397.484 219.046 397.232 219.074 396.936 219.13C396.64 219.182 396.33 219.264 396.006 219.376L395.676 218.428C396.072 218.28 396.46 218.172 396.84 218.104C397.22 218.032 397.572 217.996 397.896 217.996C398.744 217.996 399.376 218.184 399.792 218.56C400.212 218.936 400.422 219.456 400.422 220.12V222.946ZM397.674 223.672C397.946 223.672 398.212 223.6 398.472 223.456C398.732 223.312 398.942 223.11 399.102 222.85V221.452H398.358C397.726 221.452 397.274 221.558 397.002 221.77C396.73 221.978 396.594 222.266 396.594 222.634C396.594 222.97 396.684 223.228 396.864 223.408C397.048 223.584 397.318 223.672 397.674 223.672ZM402.835 224.5V223.54H403.765V219.118H402.835V218.164H404.791L405.037 219.634C405.273 219.102 405.565 218.698 405.913 218.422C406.265 218.142 406.717 218.002 407.269 218.002C407.457 218.002 407.625 218.018 407.773 218.05C407.925 218.078 408.075 218.116 408.223 218.164L407.821 219.316C407.693 219.28 407.573 219.254 407.461 219.238C407.349 219.218 407.225 219.208 407.089 219.208C406.629 219.208 406.231 219.38 405.895 219.724C405.563 220.068 405.301 220.556 405.109 221.188V223.54H406.387V224.5H402.835ZM407.035 220.498V218.938L407.203 218.164H408.223L407.995 220.498H407.035ZM415.304 217.36L415.664 218.476C415.412 218.56 415.134 218.616 414.83 218.644C414.53 218.672 414.194 218.686 413.822 218.686C414.202 218.854 414.488 219.07 414.68 219.334C414.872 219.594 414.968 219.914 414.968 220.294C414.968 220.706 414.868 221.074 414.668 221.398C414.468 221.722 414.182 221.976 413.81 222.16C413.438 222.344 412.992 222.436 412.472 222.436C412.288 222.436 412.128 222.428 411.992 222.412C411.86 222.396 411.73 222.37 411.602 222.334C411.522 222.39 411.454 222.466 411.398 222.562C411.346 222.654 411.32 222.752 411.32 222.856C411.32 222.984 411.372 223.096 411.476 223.192C411.584 223.284 411.796 223.33 412.112 223.33H413.216C413.668 223.33 414.066 223.408 414.41 223.564C414.758 223.716 415.03 223.926 415.226 224.194C415.426 224.458 415.526 224.758 415.526 225.094C415.526 225.726 415.254 226.216 414.71 226.564C414.166 226.912 413.382 227.086 412.358 227.086C411.634 227.086 411.066 227.012 410.654 226.864C410.242 226.716 409.952 226.496 409.784 226.204C409.616 225.912 409.532 225.554 409.532 225.13H410.738C410.738 225.35 410.78 225.532 410.864 225.676C410.952 225.82 411.114 225.928 411.35 226C411.586 226.072 411.926 226.108 412.37 226.108C412.814 226.108 413.166 226.07 413.426 225.994C413.69 225.922 413.88 225.816 413.996 225.676C414.116 225.54 414.176 225.376 414.176 225.184C414.176 224.928 414.064 224.73 413.84 224.59C413.616 224.45 413.316 224.38 412.94 224.38H411.854C411.254 224.38 410.814 224.258 410.534 224.014C410.258 223.77 410.12 223.486 410.12 223.162C410.12 222.946 410.18 222.738 410.3 222.538C410.424 222.338 410.6 222.164 410.828 222.016C410.448 221.816 410.17 221.574 409.994 221.29C409.822 221.002 409.736 220.654 409.736 220.246C409.736 219.798 409.848 219.406 410.072 219.07C410.296 218.734 410.606 218.472 411.002 218.284C411.402 218.096 411.862 218.002 412.382 218.002C412.89 218.002 413.314 217.976 413.654 217.924C413.994 217.868 414.29 217.79 414.542 217.69C414.798 217.59 415.052 217.48 415.304 217.36ZM412.394 218.926C411.966 218.926 411.644 219.048 411.428 219.292C411.212 219.532 411.104 219.846 411.104 220.234C411.104 220.634 411.214 220.956 411.434 221.2C411.658 221.44 411.984 221.56 412.412 221.56C412.808 221.56 413.11 221.444 413.318 221.212C413.53 220.98 413.636 220.65 413.636 220.222C413.636 219.79 413.532 219.466 413.324 219.25C413.116 219.034 412.806 218.926 412.394 218.926ZM420.802 216.22V224.26H419.47V217.6L417.646 218.722L417.07 217.78L419.614 216.22H420.802ZM422.458 223.444V224.5H417.448V223.444H422.458ZM425.807 222.616C425.807 222.324 425.911 222.074 426.119 221.866C426.327 221.654 426.583 221.548 426.887 221.548C427.195 221.548 427.451 221.654 427.655 221.866C427.863 222.074 427.967 222.324 427.967 222.616C427.967 222.916 427.863 223.172 427.655 223.384C427.451 223.596 427.195 223.702 426.887 223.702C426.583 223.702 426.327 223.596 426.119 223.384C425.911 223.172 425.807 222.916 425.807 222.616ZM425.807 218.356C425.807 218.16 425.855 217.98 425.951 217.816C426.047 217.652 426.177 217.522 426.341 217.426C426.505 217.33 426.687 217.282 426.887 217.282C427.195 217.282 427.451 217.388 427.655 217.6C427.863 217.808 427.967 218.06 427.967 218.356C427.967 218.652 427.863 218.908 427.655 219.124C427.451 219.336 427.195 219.442 426.887 219.442C426.583 219.442 426.327 219.336 426.119 219.124C425.911 218.908 425.807 218.652 425.807 218.356Z" fill="#0F172A"/> +<path d="M440 214C440 211.791 441.791 210 444 210H474C476.209 210 478 211.791 478 214V227C478 229.209 476.209 231 474 231H444C441.791 231 440 229.209 440 227V214Z" fill="#516179" fill-opacity="0.8"/> +<path d="M457.741 223.576C457.741 223.38 457.787 223.204 457.879 223.048C457.971 222.888 458.097 222.762 458.257 222.67C458.413 222.574 458.591 222.526 458.791 222.526C458.995 222.526 459.177 222.574 459.337 222.67C459.497 222.762 459.623 222.888 459.715 223.048C459.807 223.204 459.853 223.38 459.853 223.576C459.853 223.772 459.807 223.952 459.715 224.116C459.623 224.276 459.497 224.404 459.337 224.5C459.177 224.592 458.995 224.638 458.791 224.638C458.591 224.638 458.413 224.592 458.257 224.5C458.097 224.404 457.971 224.276 457.879 224.116C457.787 223.952 457.741 223.772 457.741 223.576ZM462.541 223.576C462.541 223.38 462.587 223.204 462.679 223.048C462.771 222.888 462.895 222.762 463.051 222.67C463.211 222.574 463.391 222.526 463.591 222.526C463.795 222.526 463.977 222.574 464.137 222.67C464.297 222.762 464.423 222.888 464.515 223.048C464.607 223.204 464.653 223.38 464.653 223.576C464.653 223.772 464.607 223.952 464.515 224.116C464.423 224.276 464.297 224.404 464.137 224.5C463.977 224.592 463.795 224.638 463.591 224.638C463.391 224.638 463.211 224.592 463.051 224.5C462.895 224.404 462.771 224.276 462.679 224.116C462.587 223.952 462.541 223.772 462.541 223.576ZM452.941 223.576C452.941 223.38 452.987 223.204 453.079 223.048C453.171 222.888 453.297 222.762 453.457 222.67C453.613 222.574 453.791 222.526 453.991 222.526C454.195 222.526 454.377 222.574 454.537 222.67C454.697 222.762 454.823 222.888 454.915 223.048C455.007 223.204 455.053 223.38 455.053 223.576C455.053 223.772 455.007 223.952 454.915 224.116C454.823 224.276 454.697 224.404 454.537 224.5C454.377 224.592 454.195 224.638 453.991 224.638C453.791 224.638 453.613 224.592 453.457 224.5C453.297 224.404 453.171 224.276 453.079 224.116C452.987 223.952 452.941 223.772 452.941 223.576Z" fill="white"/> +<path d="M400.422 247.946C400.422 248.194 400.46 248.376 400.536 248.492C400.612 248.604 400.734 248.688 400.902 248.744L400.602 249.656C400.29 249.62 400.022 249.534 399.798 249.398C399.574 249.262 399.404 249.056 399.288 248.78C399.056 249.072 398.762 249.292 398.406 249.44C398.05 249.588 397.67 249.662 397.266 249.662C396.626 249.662 396.118 249.482 395.742 249.122C395.37 248.758 395.184 248.282 395.184 247.694C395.184 247.026 395.444 246.512 395.964 246.152C396.484 245.792 397.228 245.612 398.196 245.612H399.102V245.198C399.102 244.79 398.978 244.496 398.73 244.316C398.486 244.136 398.14 244.046 397.692 244.046C397.484 244.046 397.232 244.074 396.936 244.13C396.64 244.182 396.33 244.264 396.006 244.376L395.676 243.428C396.072 243.28 396.46 243.172 396.84 243.104C397.22 243.032 397.572 242.996 397.896 242.996C398.744 242.996 399.376 243.184 399.792 243.56C400.212 243.936 400.422 244.456 400.422 245.12V247.946ZM397.674 248.672C397.946 248.672 398.212 248.6 398.472 248.456C398.732 248.312 398.942 248.11 399.102 247.85V246.452H398.358C397.726 246.452 397.274 246.558 397.002 246.77C396.73 246.978 396.594 247.266 396.594 247.634C396.594 247.97 396.684 248.228 396.864 248.408C397.048 248.584 397.318 248.672 397.674 248.672ZM402.835 249.5V248.54H403.765V244.118H402.835V243.164H404.791L405.037 244.634C405.273 244.102 405.565 243.698 405.913 243.422C406.265 243.142 406.717 243.002 407.269 243.002C407.457 243.002 407.625 243.018 407.773 243.05C407.925 243.078 408.075 243.116 408.223 243.164L407.821 244.316C407.693 244.28 407.573 244.254 407.461 244.238C407.349 244.218 407.225 244.208 407.089 244.208C406.629 244.208 406.231 244.38 405.895 244.724C405.563 245.068 405.301 245.556 405.109 246.188V248.54H406.387V249.5H402.835ZM407.035 245.498V243.938L407.203 243.164H408.223L407.995 245.498H407.035ZM415.304 242.36L415.664 243.476C415.412 243.56 415.134 243.616 414.83 243.644C414.53 243.672 414.194 243.686 413.822 243.686C414.202 243.854 414.488 244.07 414.68 244.334C414.872 244.594 414.968 244.914 414.968 245.294C414.968 245.706 414.868 246.074 414.668 246.398C414.468 246.722 414.182 246.976 413.81 247.16C413.438 247.344 412.992 247.436 412.472 247.436C412.288 247.436 412.128 247.428 411.992 247.412C411.86 247.396 411.73 247.37 411.602 247.334C411.522 247.39 411.454 247.466 411.398 247.562C411.346 247.654 411.32 247.752 411.32 247.856C411.32 247.984 411.372 248.096 411.476 248.192C411.584 248.284 411.796 248.33 412.112 248.33H413.216C413.668 248.33 414.066 248.408 414.41 248.564C414.758 248.716 415.03 248.926 415.226 249.194C415.426 249.458 415.526 249.758 415.526 250.094C415.526 250.726 415.254 251.216 414.71 251.564C414.166 251.912 413.382 252.086 412.358 252.086C411.634 252.086 411.066 252.012 410.654 251.864C410.242 251.716 409.952 251.496 409.784 251.204C409.616 250.912 409.532 250.554 409.532 250.13H410.738C410.738 250.35 410.78 250.532 410.864 250.676C410.952 250.82 411.114 250.928 411.35 251C411.586 251.072 411.926 251.108 412.37 251.108C412.814 251.108 413.166 251.07 413.426 250.994C413.69 250.922 413.88 250.816 413.996 250.676C414.116 250.54 414.176 250.376 414.176 250.184C414.176 249.928 414.064 249.73 413.84 249.59C413.616 249.45 413.316 249.38 412.94 249.38H411.854C411.254 249.38 410.814 249.258 410.534 249.014C410.258 248.77 410.12 248.486 410.12 248.162C410.12 247.946 410.18 247.738 410.3 247.538C410.424 247.338 410.6 247.164 410.828 247.016C410.448 246.816 410.17 246.574 409.994 246.29C409.822 246.002 409.736 245.654 409.736 245.246C409.736 244.798 409.848 244.406 410.072 244.07C410.296 243.734 410.606 243.472 411.002 243.284C411.402 243.096 411.862 243.002 412.382 243.002C412.89 243.002 413.314 242.976 413.654 242.924C413.994 242.868 414.29 242.79 414.542 242.69C414.798 242.59 415.052 242.48 415.304 242.36ZM412.394 243.926C411.966 243.926 411.644 244.048 411.428 244.292C411.212 244.532 411.104 244.846 411.104 245.234C411.104 245.634 411.214 245.956 411.434 246.2C411.658 246.44 411.984 246.56 412.412 246.56C412.808 246.56 413.11 246.444 413.318 246.212C413.53 245.98 413.636 245.65 413.636 245.222C413.636 244.79 413.532 244.466 413.324 244.25C413.116 244.034 412.806 243.926 412.394 243.926ZM419.362 241.052C419.926 241.052 420.402 241.158 420.79 241.37C421.182 241.578 421.48 241.858 421.684 242.21C421.892 242.562 421.996 242.956 421.996 243.392C421.996 243.752 421.932 244.106 421.804 244.454C421.68 244.802 421.478 245.168 421.198 245.552C420.922 245.932 420.556 246.356 420.1 246.824C419.644 247.288 419.086 247.82 418.426 248.42H422.188L422.032 249.5H416.884V248.48C417.48 247.912 417.99 247.412 418.414 246.98C418.838 246.544 419.192 246.162 419.476 245.834C419.76 245.502 419.984 245.204 420.148 244.94C420.312 244.672 420.428 244.422 420.496 244.19C420.568 243.954 420.604 243.716 420.604 243.476C420.604 243.06 420.486 242.732 420.25 242.492C420.014 242.252 419.686 242.132 419.266 242.132C418.89 242.132 418.578 242.2 418.33 242.336C418.082 242.472 417.836 242.692 417.592 242.996L416.716 242.33C417.04 241.918 417.412 241.602 417.832 241.382C418.252 241.162 418.762 241.052 419.362 241.052ZM425.807 247.616C425.807 247.324 425.911 247.074 426.119 246.866C426.327 246.654 426.583 246.548 426.887 246.548C427.195 246.548 427.451 246.654 427.655 246.866C427.863 247.074 427.967 247.324 427.967 247.616C427.967 247.916 427.863 248.172 427.655 248.384C427.451 248.596 427.195 248.702 426.887 248.702C426.583 248.702 426.327 248.596 426.119 248.384C425.911 248.172 425.807 247.916 425.807 247.616ZM425.807 243.356C425.807 243.16 425.855 242.98 425.951 242.816C426.047 242.652 426.177 242.522 426.341 242.426C426.505 242.33 426.687 242.282 426.887 242.282C427.195 242.282 427.451 242.388 427.655 242.6C427.863 242.808 427.967 243.06 427.967 243.356C427.967 243.652 427.863 243.908 427.655 244.124C427.451 244.336 427.195 244.442 426.887 244.442C426.583 244.442 426.327 244.336 426.119 244.124C425.911 243.908 425.807 243.652 425.807 243.356Z" fill="#0F172A"/> +<path d="M440 239C440 236.791 441.791 235 444 235H474C476.209 235 478 236.791 478 239V252C478 254.209 476.209 256 474 256H444C441.791 256 440 254.209 440 252V239Z" fill="#516179" fill-opacity="0.8"/> +<path d="M457.741 248.576C457.741 248.38 457.787 248.204 457.879 248.048C457.971 247.888 458.097 247.762 458.257 247.67C458.413 247.574 458.591 247.526 458.791 247.526C458.995 247.526 459.177 247.574 459.337 247.67C459.497 247.762 459.623 247.888 459.715 248.048C459.807 248.204 459.853 248.38 459.853 248.576C459.853 248.772 459.807 248.952 459.715 249.116C459.623 249.276 459.497 249.404 459.337 249.5C459.177 249.592 458.995 249.638 458.791 249.638C458.591 249.638 458.413 249.592 458.257 249.5C458.097 249.404 457.971 249.276 457.879 249.116C457.787 248.952 457.741 248.772 457.741 248.576ZM462.541 248.576C462.541 248.38 462.587 248.204 462.679 248.048C462.771 247.888 462.895 247.762 463.051 247.67C463.211 247.574 463.391 247.526 463.591 247.526C463.795 247.526 463.977 247.574 464.137 247.67C464.297 247.762 464.423 247.888 464.515 248.048C464.607 248.204 464.653 248.38 464.653 248.576C464.653 248.772 464.607 248.952 464.515 249.116C464.423 249.276 464.297 249.404 464.137 249.5C463.977 249.592 463.795 249.638 463.591 249.638C463.391 249.638 463.211 249.592 463.051 249.5C462.895 249.404 462.771 249.276 462.679 249.116C462.587 248.952 462.541 248.772 462.541 248.576ZM452.941 248.576C452.941 248.38 452.987 248.204 453.079 248.048C453.171 247.888 453.297 247.762 453.457 247.67C453.613 247.574 453.791 247.526 453.991 247.526C454.195 247.526 454.377 247.574 454.537 247.67C454.697 247.762 454.823 247.888 454.915 248.048C455.007 248.204 455.053 248.38 455.053 248.576C455.053 248.772 455.007 248.952 454.915 249.116C454.823 249.276 454.697 249.404 454.537 249.5C454.377 249.592 454.195 249.638 453.991 249.638C453.791 249.638 453.613 249.592 453.457 249.5C453.297 249.404 453.171 249.276 453.079 249.116C452.987 248.952 452.941 248.772 452.941 248.576Z" fill="white"/> +<path d="M400.422 272.946C400.422 273.194 400.46 273.376 400.536 273.492C400.612 273.604 400.734 273.688 400.902 273.744L400.602 274.656C400.29 274.62 400.022 274.534 399.798 274.398C399.574 274.262 399.404 274.056 399.288 273.78C399.056 274.072 398.762 274.292 398.406 274.44C398.05 274.588 397.67 274.662 397.266 274.662C396.626 274.662 396.118 274.482 395.742 274.122C395.37 273.758 395.184 273.282 395.184 272.694C395.184 272.026 395.444 271.512 395.964 271.152C396.484 270.792 397.228 270.612 398.196 270.612H399.102V270.198C399.102 269.79 398.978 269.496 398.73 269.316C398.486 269.136 398.14 269.046 397.692 269.046C397.484 269.046 397.232 269.074 396.936 269.13C396.64 269.182 396.33 269.264 396.006 269.376L395.676 268.428C396.072 268.28 396.46 268.172 396.84 268.104C397.22 268.032 397.572 267.996 397.896 267.996C398.744 267.996 399.376 268.184 399.792 268.56C400.212 268.936 400.422 269.456 400.422 270.12V272.946ZM397.674 273.672C397.946 273.672 398.212 273.6 398.472 273.456C398.732 273.312 398.942 273.11 399.102 272.85V271.452H398.358C397.726 271.452 397.274 271.558 397.002 271.77C396.73 271.978 396.594 272.266 396.594 272.634C396.594 272.97 396.684 273.228 396.864 273.408C397.048 273.584 397.318 273.672 397.674 273.672ZM402.835 274.5V273.54H403.765V269.118H402.835V268.164H404.791L405.037 269.634C405.273 269.102 405.565 268.698 405.913 268.422C406.265 268.142 406.717 268.002 407.269 268.002C407.457 268.002 407.625 268.018 407.773 268.05C407.925 268.078 408.075 268.116 408.223 268.164L407.821 269.316C407.693 269.28 407.573 269.254 407.461 269.238C407.349 269.218 407.225 269.208 407.089 269.208C406.629 269.208 406.231 269.38 405.895 269.724C405.563 270.068 405.301 270.556 405.109 271.188V273.54H406.387V274.5H402.835ZM407.035 270.498V268.938L407.203 268.164H408.223L407.995 270.498H407.035ZM415.304 267.36L415.664 268.476C415.412 268.56 415.134 268.616 414.83 268.644C414.53 268.672 414.194 268.686 413.822 268.686C414.202 268.854 414.488 269.07 414.68 269.334C414.872 269.594 414.968 269.914 414.968 270.294C414.968 270.706 414.868 271.074 414.668 271.398C414.468 271.722 414.182 271.976 413.81 272.16C413.438 272.344 412.992 272.436 412.472 272.436C412.288 272.436 412.128 272.428 411.992 272.412C411.86 272.396 411.73 272.37 411.602 272.334C411.522 272.39 411.454 272.466 411.398 272.562C411.346 272.654 411.32 272.752 411.32 272.856C411.32 272.984 411.372 273.096 411.476 273.192C411.584 273.284 411.796 273.33 412.112 273.33H413.216C413.668 273.33 414.066 273.408 414.41 273.564C414.758 273.716 415.03 273.926 415.226 274.194C415.426 274.458 415.526 274.758 415.526 275.094C415.526 275.726 415.254 276.216 414.71 276.564C414.166 276.912 413.382 277.086 412.358 277.086C411.634 277.086 411.066 277.012 410.654 276.864C410.242 276.716 409.952 276.496 409.784 276.204C409.616 275.912 409.532 275.554 409.532 275.13H410.738C410.738 275.35 410.78 275.532 410.864 275.676C410.952 275.82 411.114 275.928 411.35 276C411.586 276.072 411.926 276.108 412.37 276.108C412.814 276.108 413.166 276.07 413.426 275.994C413.69 275.922 413.88 275.816 413.996 275.676C414.116 275.54 414.176 275.376 414.176 275.184C414.176 274.928 414.064 274.73 413.84 274.59C413.616 274.45 413.316 274.38 412.94 274.38H411.854C411.254 274.38 410.814 274.258 410.534 274.014C410.258 273.77 410.12 273.486 410.12 273.162C410.12 272.946 410.18 272.738 410.3 272.538C410.424 272.338 410.6 272.164 410.828 272.016C410.448 271.816 410.17 271.574 409.994 271.29C409.822 271.002 409.736 270.654 409.736 270.246C409.736 269.798 409.848 269.406 410.072 269.07C410.296 268.734 410.606 268.472 411.002 268.284C411.402 268.096 411.862 268.002 412.382 268.002C412.89 268.002 413.314 267.976 413.654 267.924C413.994 267.868 414.29 267.79 414.542 267.69C414.798 267.59 415.052 267.48 415.304 267.36ZM412.394 268.926C411.966 268.926 411.644 269.048 411.428 269.292C411.212 269.532 411.104 269.846 411.104 270.234C411.104 270.634 411.214 270.956 411.434 271.2C411.658 271.44 411.984 271.56 412.412 271.56C412.808 271.56 413.11 271.444 413.318 271.212C413.53 270.98 413.636 270.65 413.636 270.222C413.636 269.79 413.532 269.466 413.324 269.25C413.116 269.034 412.806 268.926 412.394 268.926ZM419.338 266.052C419.886 266.052 420.358 266.148 420.754 266.34C421.15 266.532 421.454 266.786 421.666 267.102C421.878 267.418 421.984 267.766 421.984 268.146C421.984 268.486 421.912 268.79 421.768 269.058C421.624 269.326 421.428 269.548 421.18 269.724C420.932 269.9 420.652 270.024 420.34 270.096C420.688 270.132 421.006 270.228 421.294 270.384C421.582 270.54 421.812 270.762 421.984 271.05C422.16 271.338 422.248 271.7 422.248 272.136C422.248 272.608 422.124 273.036 421.876 273.42C421.632 273.8 421.284 274.102 420.832 274.326C420.384 274.55 419.856 274.662 419.248 274.662C418.728 274.662 418.232 274.566 417.76 274.374C417.292 274.182 416.894 273.892 416.566 273.504L417.388 272.784C417.624 273.06 417.9 273.264 418.216 273.396C418.532 273.528 418.856 273.594 419.188 273.594C419.7 273.594 420.106 273.462 420.406 273.198C420.706 272.93 420.856 272.56 420.856 272.088C420.856 271.728 420.788 271.444 420.652 271.236C420.52 271.024 420.334 270.872 420.094 270.78C419.854 270.688 419.576 270.642 419.26 270.642H418.546L418.702 269.652H419.206C419.466 269.652 419.706 269.606 419.926 269.514C420.146 269.418 420.322 269.272 420.454 269.076C420.59 268.88 420.658 268.636 420.658 268.344C420.658 267.94 420.52 267.63 420.244 267.414C419.972 267.198 419.634 267.09 419.23 267.09C418.886 267.09 418.578 267.154 418.306 267.282C418.038 267.406 417.774 267.59 417.514 267.834L416.8 267.072C417.164 266.724 417.558 266.468 417.982 266.304C418.406 266.136 418.858 266.052 419.338 266.052ZM425.807 272.616C425.807 272.324 425.911 272.074 426.119 271.866C426.327 271.654 426.583 271.548 426.887 271.548C427.195 271.548 427.451 271.654 427.655 271.866C427.863 272.074 427.967 272.324 427.967 272.616C427.967 272.916 427.863 273.172 427.655 273.384C427.451 273.596 427.195 273.702 426.887 273.702C426.583 273.702 426.327 273.596 426.119 273.384C425.911 273.172 425.807 272.916 425.807 272.616ZM425.807 268.356C425.807 268.16 425.855 267.98 425.951 267.816C426.047 267.652 426.177 267.522 426.341 267.426C426.505 267.33 426.687 267.282 426.887 267.282C427.195 267.282 427.451 267.388 427.655 267.6C427.863 267.808 427.967 268.06 427.967 268.356C427.967 268.652 427.863 268.908 427.655 269.124C427.451 269.336 427.195 269.442 426.887 269.442C426.583 269.442 426.327 269.336 426.119 269.124C425.911 268.908 425.807 268.652 425.807 268.356Z" fill="#0F172A"/> +<path d="M440 264C440 261.791 441.791 260 444 260H474C476.209 260 478 261.791 478 264V277C478 279.209 476.209 281 474 281H444C441.791 281 440 279.209 440 277V264Z" fill="#516179" fill-opacity="0.8"/> +<path d="M457.741 273.576C457.741 273.38 457.787 273.204 457.879 273.048C457.971 272.888 458.097 272.762 458.257 272.67C458.413 272.574 458.591 272.526 458.791 272.526C458.995 272.526 459.177 272.574 459.337 272.67C459.497 272.762 459.623 272.888 459.715 273.048C459.807 273.204 459.853 273.38 459.853 273.576C459.853 273.772 459.807 273.952 459.715 274.116C459.623 274.276 459.497 274.404 459.337 274.5C459.177 274.592 458.995 274.638 458.791 274.638C458.591 274.638 458.413 274.592 458.257 274.5C458.097 274.404 457.971 274.276 457.879 274.116C457.787 273.952 457.741 273.772 457.741 273.576ZM462.541 273.576C462.541 273.38 462.587 273.204 462.679 273.048C462.771 272.888 462.895 272.762 463.051 272.67C463.211 272.574 463.391 272.526 463.591 272.526C463.795 272.526 463.977 272.574 464.137 272.67C464.297 272.762 464.423 272.888 464.515 273.048C464.607 273.204 464.653 273.38 464.653 273.576C464.653 273.772 464.607 273.952 464.515 274.116C464.423 274.276 464.297 274.404 464.137 274.5C463.977 274.592 463.795 274.638 463.591 274.638C463.391 274.638 463.211 274.592 463.051 274.5C462.895 274.404 462.771 274.276 462.679 274.116C462.587 273.952 462.541 273.772 462.541 273.576ZM452.941 273.576C452.941 273.38 452.987 273.204 453.079 273.048C453.171 272.888 453.297 272.762 453.457 272.67C453.613 272.574 453.791 272.526 453.991 272.526C454.195 272.526 454.377 272.574 454.537 272.67C454.697 272.762 454.823 272.888 454.915 273.048C455.007 273.204 455.053 273.38 455.053 273.576C455.053 273.772 455.007 273.952 454.915 274.116C454.823 274.276 454.697 274.404 454.537 274.5C454.377 274.592 454.195 274.638 453.991 274.638C453.791 274.638 453.613 274.592 453.457 274.5C453.297 274.404 453.171 274.276 453.079 274.116C452.987 273.952 452.941 273.772 452.941 273.576Z" fill="white"/> +<path d="M557 206L552 203.113L552 208.887L557 206ZM542 206.5L552.5 206.5L552.5 205.5L542 205.5L542 206.5Z" fill="white"/> +<rect x="573" y="56.5" width="296" height="320" rx="12" fill="#46C195" fill-opacity="0.2"/> +<rect x="615" y="180" width="50" height="50" rx="12" fill="#46C195"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M644 196C641.239 196 639 198.239 639 201C639 201.411 639.05 201.809 639.142 202.188L639.147 202.208C639.202 202.436 639.251 202.636 639.284 202.798C639.314 202.951 639.351 203.162 639.336 203.382C639.316 203.68 639.254 203.9 639.117 204.165C638.97 204.448 638.722 204.695 638.516 204.899C638.495 204.919 638.475 204.94 638.455 204.959L632.207 211.207C631.769 211.645 631.769 212.355 632.207 212.793C632.645 213.231 633.355 213.231 633.793 212.793L640.041 206.545C640.061 206.525 640.081 206.505 640.101 206.484C640.306 206.279 640.553 206.03 640.835 205.883C641.101 205.746 641.32 205.684 641.619 205.664C641.838 205.649 642.049 205.686 642.203 205.717C642.364 205.749 642.564 205.798 642.793 205.853L642.812 205.858C643.192 205.951 643.59 206 644 206C646.762 206 649 203.761 649 201C649 200.812 648.99 200.627 648.97 200.445L647.316 202.098C647.138 202.277 646.964 202.451 646.805 202.586C646.63 202.734 646.408 202.894 646.118 202.988C645.717 203.118 645.284 203.118 644.882 202.988C644.592 202.894 644.371 202.734 644.196 202.586C644.037 202.451 643.863 202.277 643.684 202.098L642.902 201.316C642.723 201.137 642.55 200.964 642.414 200.804C642.266 200.63 642.106 200.408 642.012 200.118C641.882 199.716 641.882 199.284 642.012 198.882C642.106 198.592 642.266 198.37 642.414 198.196C642.55 198.036 642.723 197.863 642.902 197.684L644.556 196.03C644.373 196.01 644.188 196 644 196ZM637 201C637 197.134 640.134 194 644 194C645.026 194 646.002 194.221 646.882 194.619C647.181 194.754 647.395 195.028 647.454 195.351C647.513 195.674 647.409 196.005 647.177 196.237L644.339 199.076C644.129 199.285 644.015 199.401 643.939 199.49C643.936 199.493 643.933 199.497 643.931 199.5C643.933 199.503 643.936 199.507 643.939 199.51C644.015 199.599 644.129 199.715 644.339 199.924L645.076 200.662C645.286 200.871 645.401 200.985 645.49 201.061C645.494 201.064 645.497 201.067 645.5 201.07C645.503 201.067 645.507 201.064 645.51 201.061C645.6 200.985 645.715 200.871 645.924 200.662L648.763 197.823C648.995 197.591 649.326 197.488 649.649 197.546C649.972 197.605 650.246 197.819 650.381 198.118C650.779 198.998 651 199.975 651 201C651 204.866 647.866 208 644 208C643.429 208 642.872 207.931 642.338 207.801C642.085 207.74 641.925 207.701 641.808 207.677C641.787 207.673 641.769 207.67 641.755 207.667C641.751 207.671 641.747 207.674 641.743 207.678C641.68 207.734 641.599 207.815 641.455 207.959L635.207 214.207C633.988 215.426 632.012 215.426 630.793 214.207C629.574 212.988 629.574 211.012 630.793 209.793L637.041 203.545C637.185 203.401 637.266 203.32 637.322 203.258C637.326 203.253 637.33 203.249 637.333 203.245C637.33 203.231 637.327 203.213 637.323 203.192C637.299 203.075 637.261 202.915 637.199 202.662C637.069 202.128 637 201.571 637 201ZM637.35 203.229C637.35 203.229 637.35 203.228 637.35 203.229C637.348 203.233 637.347 203.239 637.345 203.245C637.345 203.246 637.345 203.245 637.345 203.245C637.345 203.246 637.345 203.247 637.344 203.247C637.343 203.253 637.341 203.259 637.34 203.264C637.34 203.264 637.34 203.265 637.34 203.265" fill="white"/> +<path d="M620.762 242.243V240.878H628.539V242.243H625.48V251H623.968V242.243H620.762ZM634.475 251.14C633.555 251.14 632.76 250.937 632.088 250.531C631.42 250.12 630.905 249.53 630.541 248.76C630.181 247.985 630.002 247.052 630.002 245.96C630.002 244.859 630.184 243.921 630.548 243.146C630.916 242.367 631.434 241.772 632.102 241.361C632.774 240.946 633.565 240.738 634.475 240.738C635.38 240.738 636.164 240.946 636.827 241.361C637.494 241.772 638.007 242.367 638.367 243.146C638.731 243.921 638.913 244.859 638.913 245.96C638.913 247.052 638.733 247.983 638.374 248.753C638.014 249.523 637.501 250.113 636.834 250.524C636.171 250.935 635.385 251.14 634.475 251.14ZM634.475 249.859C635.063 249.859 635.564 249.724 635.98 249.453C636.4 249.182 636.719 248.76 636.939 248.186C637.158 247.612 637.268 246.877 637.268 245.981C637.268 245.062 637.156 244.31 636.932 243.727C636.712 243.144 636.393 242.714 635.973 242.439C635.557 242.159 635.058 242.019 634.475 242.019C633.891 242.019 633.387 242.159 632.963 242.439C632.543 242.719 632.218 243.151 631.99 243.734C631.761 244.317 631.647 245.066 631.647 245.981C631.647 246.882 631.761 247.619 631.99 248.193C632.218 248.762 632.543 249.182 632.963 249.453C633.387 249.724 633.891 249.859 634.475 249.859ZM645.297 251.14C644.378 251.14 643.582 250.937 642.91 250.531C642.243 250.12 641.727 249.53 641.363 248.76C641.004 247.985 640.824 247.052 640.824 245.96C640.824 244.859 641.006 243.921 641.37 243.146C641.739 242.367 642.257 241.772 642.924 241.361C643.596 240.946 644.387 240.738 645.297 240.738C646.202 240.738 646.986 240.946 647.649 241.361C648.316 241.772 648.83 242.367 649.189 243.146C649.553 243.921 649.735 244.859 649.735 245.96C649.735 247.052 649.555 247.983 649.196 248.753C648.837 249.523 648.323 250.113 647.656 250.524C646.993 250.935 646.207 251.14 645.297 251.14ZM645.297 249.859C645.885 249.859 646.387 249.724 646.802 249.453C647.222 249.182 647.542 248.76 647.761 248.186C647.98 247.612 648.09 246.877 648.09 245.981C648.09 245.062 647.978 244.31 647.754 243.727C647.535 243.144 647.215 242.714 646.795 242.439C646.38 242.159 645.88 242.019 645.297 242.019C644.714 242.019 644.21 242.159 643.785 242.439C643.365 242.719 643.041 243.151 642.812 243.734C642.583 244.317 642.469 245.066 642.469 245.981C642.469 246.882 642.583 247.619 642.812 248.193C643.041 248.762 643.365 249.182 643.785 249.453C644.21 249.724 644.714 249.859 645.297 249.859ZM652.254 240.878H653.773V249.684H658.449V251H652.247L652.254 240.878Z" fill="white"/> +<g filter="url(#filter0_ddd_336_13394)"> +<rect x="689" y="80.5" width="20" height="20" rx="4" fill="white"/> +<path d="M698.872 89.3931L696.267 92.4329C696.151 92.5566 696.079 92.7736 696.144 92.9294C696.176 93.0079 696.231 93.0748 696.302 93.1216C696.372 93.1685 696.455 93.1931 696.54 93.1923C696.639 93.1923 696.72 93.1579 696.825 93.0514L700.049 89.7098C700.152 89.6073 700.21 89.4683 700.211 89.3232C700.211 89.1781 700.155 89.0385 700.053 88.9351L698.872 89.3931Z" fill="#AA142D"/> +<path d="M698.818 89.3295L701.229 86.2793C701.364 86.0999 701.427 86.0059 701.364 85.8532C701.33 85.7724 701.274 85.7027 701.203 85.6522C701.131 85.6017 701.047 85.5725 700.96 85.568C700.91 85.5652 700.86 85.5726 700.814 85.5898C700.767 85.607 700.724 85.6336 700.688 85.6679L697.606 88.9346C697.371 89.1696 697.371 89.4735 697.607 89.7094L701.918 94.2306C701.955 94.2684 701.999 94.2977 702.048 94.3163C702.097 94.3349 702.149 94.3424 702.202 94.3382C702.376 94.3382 702.489 94.2355 702.565 94.0839C702.63 93.928 702.558 93.7737 702.438 93.612L698.818 89.3295" fill="#BDB9B4"/> +<path d="M700.053 88.9348L695.843 84.5042C695.843 84.5042 695.688 84.3165 695.525 84.3126C695.442 84.3107 695.361 84.3336 695.291 84.3784C695.221 84.4232 695.166 84.4879 695.134 84.564C695.07 84.7168 695.116 84.824 695.255 85.0239L698.873 89.393L700.053 88.9348Z" fill="#AA142D"/> +<path d="M699.835 96.6873C699.812 96.6871 699.788 96.6808 699.768 96.669C699.747 96.6571 699.73 96.64 699.719 96.6195L699.194 95.7477L698.666 96.6195C698.654 96.6403 698.637 96.6575 698.616 96.6694C698.596 96.6813 698.572 96.6875 698.549 96.6873C698.511 96.6875 698.474 96.6727 698.447 96.6462C698.42 96.6197 698.405 96.5835 698.404 96.5457C698.404 96.5185 698.411 96.4919 698.426 96.4689L699.037 95.4896L698.537 94.6887C698.524 94.6652 698.516 94.6389 698.515 94.6119C698.516 94.5741 698.531 94.5381 698.558 94.5116C698.585 94.4852 698.622 94.4705 698.66 94.4708C698.683 94.4699 698.706 94.4754 698.727 94.4868C698.748 94.4981 698.765 94.5149 698.776 94.5352L699.194 95.2383L699.608 94.5352C699.619 94.515 699.636 94.4982 699.656 94.4868C699.676 94.4755 699.699 94.4699 699.722 94.4708C699.741 94.4701 699.759 94.4733 699.777 94.4801C699.795 94.487 699.811 94.4973 699.824 94.5105C699.838 94.5237 699.848 94.5394 699.855 94.5569C699.863 94.5743 699.866 94.593 699.866 94.6119C699.866 94.6386 699.86 94.6649 699.848 94.6887L699.35 95.4899L699.955 96.4693C699.972 96.4912 699.981 96.5183 699.98 96.546C699.979 96.5837 699.963 96.6195 699.936 96.6458C699.909 96.6722 699.873 96.687 699.835 96.6873Z" fill="#0F172A"/> +<path d="M697.173 95.1206C697.236 95.1206 697.278 95.1625 697.308 95.2376C697.319 95.2043 697.34 95.1752 697.369 95.1543C697.397 95.1335 697.431 95.1217 697.466 95.1206H697.969C697.987 95.1206 698.005 95.1241 698.022 95.131C698.039 95.1379 698.054 95.148 698.067 95.1608C698.08 95.1736 698.09 95.1888 698.097 95.2056C698.104 95.2223 698.107 95.2402 698.107 95.2583V95.5231C698.11 95.5417 698.108 95.5608 698.102 95.5787C698.096 95.5966 698.086 95.6128 698.072 95.6262C698.059 95.6395 698.043 95.6496 698.025 95.6556C698.007 95.6616 697.988 95.6634 697.969 95.6608C697.951 95.6632 697.932 95.6612 697.914 95.6552C697.896 95.6491 697.88 95.639 697.867 95.6257C697.853 95.6124 697.843 95.5962 697.837 95.5784C697.831 95.5607 697.829 95.5417 697.832 95.5231V95.3962H697.475C697.463 95.3948 697.45 95.3962 697.439 95.4005C697.427 95.4047 697.416 95.4115 697.408 95.4205C697.399 95.4295 697.393 95.4403 697.389 95.4522C697.385 95.464 697.384 95.4766 697.386 95.4889V96.4081H697.724C697.742 96.4077 697.76 96.4109 697.777 96.4177C697.794 96.4244 697.81 96.4345 697.823 96.4473C697.836 96.4602 697.847 96.4755 697.854 96.4924C697.861 96.5093 697.864 96.5274 697.864 96.5457C697.864 96.5641 697.861 96.5822 697.854 96.5991C697.847 96.616 697.836 96.6313 697.823 96.6441C697.81 96.657 697.794 96.6671 697.777 96.6738C697.76 96.6805 697.742 96.6838 697.724 96.6834H696.832C696.796 96.6827 696.762 96.6678 696.737 96.6421C696.711 96.6163 696.697 96.5818 696.697 96.5457C696.697 96.5097 696.711 96.4751 696.737 96.4494C696.762 96.4237 696.796 96.4088 696.832 96.4081H697.11V95.396H696.865C696.829 95.395 696.795 95.38 696.77 95.3543C696.745 95.3286 696.731 95.2942 696.731 95.2583C696.731 95.2225 696.745 95.1881 696.77 95.1624C696.795 95.1367 696.829 95.1217 696.865 95.1206H697.173Z" fill="#0F172A"/> +<path d="M700.947 95.1203C700.966 95.1179 700.985 95.1198 701.002 95.1259C701.02 95.132 701.036 95.1421 701.05 95.1554C701.063 95.1687 701.073 95.1848 701.079 95.2026C701.085 95.2204 701.087 95.2394 701.085 95.258V96.4076H701.426C701.444 96.4072 701.462 96.4105 701.48 96.4172C701.497 96.424 701.512 96.4341 701.525 96.4469C701.538 96.4597 701.549 96.475 701.556 96.4919C701.563 96.5088 701.567 96.527 701.567 96.5453C701.567 96.5636 701.563 96.5818 701.556 96.5987C701.549 96.6156 701.538 96.6309 701.525 96.6437C701.512 96.6565 701.497 96.6666 701.48 96.6733C701.462 96.6801 701.444 96.6834 701.426 96.683H700.459C700.441 96.6834 700.422 96.6801 700.405 96.6733C700.388 96.6666 700.373 96.6565 700.36 96.6437C700.347 96.6309 700.336 96.6156 700.329 96.5987C700.322 96.5818 700.318 96.5636 700.318 96.5453C700.318 96.527 700.322 96.5088 700.329 96.4919C700.336 96.475 700.347 96.4597 700.36 96.4469C700.373 96.4341 700.388 96.424 700.405 96.4172C700.422 96.4105 700.441 96.4072 700.459 96.4076H700.809V95.3957H700.521C700.484 95.3957 700.449 95.3812 700.423 95.3554C700.398 95.3296 700.383 95.2945 700.383 95.258C700.383 95.2215 700.398 95.1864 700.423 95.1606C700.449 95.1348 700.484 95.1203 700.521 95.1203H700.947ZM701.09 94.5248C701.09 94.5641 701.078 94.6026 701.057 94.6354C701.035 94.6682 701.004 94.6937 700.968 94.7089C700.932 94.7241 700.892 94.7281 700.853 94.7205C700.815 94.7129 700.779 94.694 700.751 94.6662C700.724 94.6384 700.705 94.603 700.697 94.5644C700.69 94.5259 700.694 94.486 700.709 94.4497C700.724 94.4135 700.75 94.3826 700.782 94.3609C700.815 94.3393 700.854 94.3279 700.893 94.3281C700.945 94.3289 700.994 94.3498 701.031 94.3866C701.068 94.4233 701.089 94.4729 701.09 94.5248Z" fill="#0F172A"/> +<path d="M703.176 95.2584C703.176 95.2767 703.173 95.295 703.166 95.3122L702.664 96.5996C702.655 96.6253 702.637 96.6473 702.615 96.6625C702.592 96.6776 702.565 96.6849 702.538 96.6833H702.337C702.309 96.6857 702.282 96.6788 702.258 96.6636C702.235 96.6484 702.218 96.6259 702.208 96.5996L701.7 95.3122C701.691 95.2956 701.687 95.2771 701.688 95.2584C701.688 95.24 701.691 95.2219 701.698 95.205C701.706 95.1881 701.716 95.1728 701.729 95.16C701.742 95.1472 701.758 95.1371 701.775 95.1303C701.792 95.1236 701.81 95.1203 701.828 95.1206C701.856 95.1199 701.884 95.1283 701.906 95.1445C701.929 95.1607 701.946 95.1839 701.954 95.2105L702.439 96.4319L702.909 95.2105C702.917 95.1839 702.934 95.1607 702.957 95.1445C702.98 95.1283 703.007 95.1199 703.035 95.1206C703.053 95.1203 703.071 95.1236 703.088 95.1303C703.105 95.1371 703.121 95.1472 703.134 95.16C703.147 95.1728 703.158 95.1881 703.165 95.205C703.172 95.2219 703.175 95.24 703.176 95.2584Z" fill="#0F172A"/> +<path d="M695.923 95.1208C695.961 95.1186 695.998 95.1244 696.033 95.138C696.069 95.1516 696.1 95.1725 696.127 95.1995C696.153 95.2264 696.173 95.2586 696.186 95.294C696.199 95.3294 696.204 95.3672 696.201 95.4047V96.6835H695C694.977 96.6851 694.955 96.6819 694.933 96.6742C694.912 96.6664 694.893 96.6543 694.876 96.6387C694.86 96.623 694.847 96.604 694.838 96.5831C694.83 96.5621 694.826 96.5396 694.826 96.5169V96.0765C694.826 96.01 694.85 95.9456 694.894 95.8951C694.937 95.8446 694.997 95.8112 695.063 95.8011L695.925 95.6804V95.3962H694.966C694.948 95.3969 694.93 95.3937 694.912 95.3871C694.895 95.3804 694.88 95.3703 694.867 95.3574C694.854 95.3445 694.843 95.3291 694.836 95.3121C694.829 95.2951 694.826 95.2769 694.826 95.2585C694.826 95.2394 694.829 95.2204 694.836 95.2028C694.844 95.1851 694.855 95.1693 694.869 95.1562C694.882 95.1432 694.899 95.1333 694.917 95.1271C694.935 95.121 694.954 95.1189 694.973 95.1208H695.923ZM695.925 96.4081V95.9587L695.102 96.0735V96.4081H695.925Z" fill="#0F172A"/> +</g> +<path d="M717.581 95.5L721.242 85.378H722.705L726.366 95.5H725.043L724.049 92.728H719.912L718.897 95.5H717.581ZM720.241 91.538H723.706L721.984 86.659L720.241 91.538ZM728.01 95.5V88.262H729.242V89.655C729.364 89.3097 729.534 89.025 729.753 88.801C729.973 88.5723 730.222 88.402 730.502 88.29C730.787 88.178 731.081 88.122 731.384 88.122C731.492 88.122 731.597 88.129 731.699 88.143C731.802 88.157 731.881 88.1803 731.937 88.213V89.466C731.867 89.4333 731.776 89.4123 731.664 89.403C731.557 89.389 731.466 89.382 731.391 89.382C731.102 89.3633 730.831 89.382 730.579 89.438C730.327 89.4893 730.106 89.5803 729.914 89.711C729.723 89.8417 729.571 90.0143 729.459 90.229C729.352 90.439 729.298 90.6957 729.298 90.999V95.5H728.01ZM741.425 95.5H739.822L737.078 91.328L734.376 95.5H732.948L736.378 90.334L733.067 85.378H734.663L737.183 89.235L739.682 85.378H741.11L737.876 90.201L741.425 95.5ZM744.45 88.262V95.5H743.197V88.262H744.45ZM744.478 85.385V86.729H743.155V85.385H744.478ZM752.383 88.262L749.772 95.5H748.596L745.978 88.262H747.196L749.121 93.876H749.24L751.172 88.262H752.383Z" fill="#CBD5E1"/> +<g filter="url(#filter1_ddd_336_13394)"> +<rect x="689" y="108.5" width="20" height="20" rx="4" fill="white"/> +<rect x="694.311" y="111.801" width="9.37891" height="13.3984" fill="url(#pattern0)"/> +</g> +<path d="M718.337 123.5V113.378H721.592C722.824 113.378 723.755 113.604 724.385 114.057C725.02 114.51 725.337 115.163 725.337 116.017C725.337 116.554 725.197 117.013 724.917 117.396C724.642 117.779 724.156 118.094 723.461 118.341C723.858 118.425 724.191 118.546 724.462 118.705C724.737 118.864 724.957 119.05 725.12 119.265C725.288 119.475 725.407 119.708 725.477 119.965C725.552 120.222 725.589 120.492 725.589 120.777C725.589 121.692 725.262 122.375 724.609 122.828C723.96 123.276 722.99 123.5 721.697 123.5H718.337ZM719.632 122.422H721.683C722.528 122.422 723.169 122.284 723.608 122.009C724.051 121.729 724.273 121.3 724.273 120.721C724.273 120.296 724.163 119.953 723.944 119.692C723.729 119.426 723.438 119.232 723.069 119.111C722.7 118.99 722.29 118.929 721.837 118.929H719.632V122.422ZM719.632 117.83H721.837C722.103 117.83 722.367 117.804 722.628 117.753C722.889 117.702 723.13 117.613 723.349 117.487C723.568 117.361 723.746 117.193 723.881 116.983C724.016 116.768 724.084 116.5 724.084 116.178C724.084 115.59 723.858 115.156 723.405 114.876C722.957 114.591 722.374 114.449 721.655 114.449H719.632V117.83ZM728.864 116.262V123.5H727.611V116.262H728.864ZM728.892 113.385V114.729H727.569V113.385H728.892ZM731.223 123.5V116.262H732.476V117.312C732.593 117.111 732.754 116.922 732.959 116.745C733.169 116.568 733.421 116.425 733.715 116.318C734.014 116.211 734.357 116.157 734.744 116.157C735.202 116.157 735.622 116.248 736.004 116.43C736.392 116.612 736.7 116.897 736.928 117.284C737.162 117.667 737.278 118.161 737.278 118.768V123.5H735.983V118.894C735.983 118.329 735.834 117.912 735.535 117.641C735.241 117.366 734.859 117.228 734.387 117.228C734.061 117.228 733.755 117.282 733.47 117.389C733.186 117.492 732.955 117.65 732.777 117.865C732.6 118.075 732.511 118.341 732.511 118.663V123.5H731.223ZM742.355 125.894C741.217 125.894 740.33 125.728 739.695 125.397C739.065 125.07 738.75 124.601 738.75 123.99C738.75 123.729 738.811 123.507 738.932 123.325C739.054 123.138 739.198 122.984 739.366 122.863C739.534 122.742 739.691 122.646 739.835 122.576C739.98 122.501 740.073 122.448 740.115 122.415C740.036 122.368 739.936 122.31 739.814 122.24C739.698 122.17 739.595 122.077 739.506 121.96C739.418 121.843 739.373 121.692 739.373 121.505C739.373 121.276 739.481 121.066 739.695 120.875C739.91 120.684 740.227 120.541 740.647 120.448C740.223 120.233 739.891 119.949 739.653 119.594C739.415 119.239 739.296 118.854 739.296 118.439C739.296 117.972 739.422 117.566 739.674 117.221C739.931 116.876 740.295 116.612 740.766 116.43C741.238 116.243 741.798 116.15 742.446 116.15C742.918 116.15 743.314 116.206 743.636 116.318C743.958 116.425 744.257 116.579 744.532 116.78C744.607 116.747 744.724 116.698 744.882 116.633C745.046 116.563 745.225 116.488 745.421 116.409C745.617 116.33 745.799 116.255 745.967 116.185C746.14 116.11 746.278 116.054 746.38 116.017L746.373 117.214L745.106 117.452C745.176 117.601 745.232 117.765 745.274 117.942C745.321 118.119 745.344 118.285 745.344 118.439C745.344 118.864 745.232 119.249 745.008 119.594C744.789 119.939 744.455 120.215 744.007 120.42C743.559 120.625 742.999 120.728 742.327 120.728C742.271 120.728 742.199 120.728 742.11 120.728C742.026 120.723 741.954 120.719 741.893 120.714C741.399 120.728 741.058 120.784 740.871 120.882C740.685 120.975 740.591 121.08 740.591 121.197C740.591 121.332 740.696 121.426 740.906 121.477C741.116 121.528 741.473 121.573 741.977 121.61C742.159 121.619 742.383 121.631 742.649 121.645C742.92 121.659 743.219 121.678 743.545 121.701C744.306 121.748 744.892 121.941 745.302 122.282C745.713 122.623 745.918 123.082 745.918 123.661C745.918 124.319 745.624 124.856 745.036 125.271C744.448 125.686 743.555 125.894 742.355 125.894ZM742.586 125.061C743.254 125.061 743.765 124.961 744.119 124.76C744.474 124.559 744.651 124.256 744.651 123.85C744.651 123.561 744.542 123.325 744.322 123.143C744.108 122.956 743.779 122.849 743.335 122.821L741.305 122.688C741.123 122.679 740.934 122.725 740.738 122.828C740.542 122.926 740.374 123.064 740.234 123.241C740.099 123.418 740.031 123.619 740.031 123.843C740.031 124.226 740.239 124.524 740.654 124.739C741.074 124.954 741.718 125.061 742.586 125.061ZM742.362 119.895C742.885 119.895 743.305 119.774 743.622 119.531C743.944 119.288 744.105 118.929 744.105 118.453C744.105 117.963 743.944 117.59 743.622 117.333C743.305 117.076 742.885 116.948 742.362 116.948C741.835 116.948 741.408 117.079 741.081 117.34C740.759 117.597 740.598 117.968 740.598 118.453C740.598 118.91 740.752 119.265 741.06 119.517C741.368 119.769 741.802 119.895 742.362 119.895ZM754.928 123.64C754.452 123.64 753.99 123.582 753.542 123.465C753.094 123.344 752.688 123.164 752.324 122.926C751.96 122.688 751.657 122.394 751.414 122.044C751.176 121.689 751.027 121.279 750.966 120.812H752.324C752.408 121.195 752.574 121.517 752.821 121.778C753.073 122.039 753.379 122.235 753.738 122.366C754.102 122.497 754.497 122.562 754.921 122.562C755.397 122.562 755.822 122.497 756.195 122.366C756.573 122.231 756.87 122.039 757.084 121.792C757.304 121.545 757.413 121.248 757.413 120.903C757.413 120.6 757.343 120.336 757.203 120.112C757.068 119.888 756.87 119.701 756.608 119.552C756.347 119.403 756.03 119.286 755.656 119.202L753.668 118.74C752.922 118.577 752.336 118.285 751.911 117.865C751.491 117.445 751.277 116.89 751.267 116.199C751.267 115.616 751.421 115.102 751.729 114.659C752.042 114.216 752.469 113.87 753.01 113.623C753.556 113.376 754.179 113.252 754.879 113.252C755.687 113.252 756.366 113.39 756.916 113.665C757.467 113.94 757.882 114.302 758.162 114.75C758.442 115.198 758.585 115.676 758.589 116.185H757.252C757.21 115.728 757.075 115.366 756.846 115.1C756.622 114.829 756.34 114.638 755.999 114.526C755.659 114.409 755.288 114.351 754.886 114.351C754.597 114.351 754.317 114.388 754.046 114.463C753.78 114.533 753.54 114.64 753.325 114.785C753.115 114.93 752.947 115.109 752.821 115.324C752.695 115.539 752.632 115.788 752.632 116.073C752.632 116.437 752.756 116.733 753.003 116.962C753.255 117.191 753.71 117.387 754.368 117.55L756.293 118.012C756.797 118.119 757.208 118.278 757.525 118.488C757.847 118.698 758.095 118.936 758.267 119.202C758.445 119.468 758.566 119.746 758.631 120.035C758.701 120.324 758.736 120.609 758.736 120.889C758.736 121.398 758.585 121.862 758.281 122.282C757.978 122.697 757.542 123.029 756.972 123.276C756.408 123.519 755.726 123.64 754.928 123.64ZM761.583 120.182C761.583 120.639 761.658 121.055 761.807 121.428C761.961 121.797 762.192 122.091 762.5 122.31C762.813 122.529 763.202 122.639 763.669 122.639C764.131 122.639 764.528 122.532 764.859 122.317C765.195 122.102 765.41 121.792 765.503 121.386H766.728C766.639 121.876 766.443 122.289 766.14 122.625C765.837 122.961 765.47 123.215 765.041 123.388C764.612 123.556 764.164 123.64 763.697 123.64C763.02 123.64 762.425 123.493 761.912 123.199C761.399 122.905 760.997 122.483 760.708 121.932C760.419 121.381 760.274 120.721 760.274 119.951C760.274 119.19 760.405 118.523 760.666 117.949C760.927 117.375 761.305 116.927 761.8 116.605C762.299 116.283 762.899 116.122 763.599 116.122C764.28 116.122 764.857 116.269 765.328 116.563C765.799 116.857 766.156 117.272 766.399 117.809C766.646 118.341 766.77 118.973 766.77 119.706V120.182H761.583ZM761.59 119.307H765.524C765.524 118.892 765.454 118.518 765.314 118.187C765.174 117.851 764.959 117.587 764.67 117.396C764.385 117.2 764.026 117.102 763.592 117.102C763.139 117.102 762.764 117.212 762.465 117.431C762.171 117.646 761.949 117.923 761.8 118.264C761.655 118.6 761.585 118.948 761.59 119.307ZM770.468 123.64C770.001 123.64 769.588 123.558 769.229 123.395C768.869 123.227 768.587 122.987 768.382 122.674C768.181 122.361 768.081 121.983 768.081 121.54C768.081 120.737 768.361 120.14 768.921 119.748C769.481 119.356 770.379 119.146 771.616 119.118L772.911 119.083V118.516C772.911 118.073 772.778 117.723 772.512 117.466C772.246 117.205 771.833 117.074 771.273 117.074C770.857 117.079 770.491 117.177 770.174 117.368C769.861 117.559 769.658 117.872 769.565 118.306H768.424C768.452 117.835 768.585 117.438 768.823 117.116C769.061 116.789 769.392 116.542 769.817 116.374C770.246 116.206 770.752 116.122 771.336 116.122C771.966 116.122 772.488 116.211 772.904 116.388C773.324 116.565 773.636 116.827 773.842 117.172C774.052 117.517 774.157 117.942 774.157 118.446V123.5H773.051L772.96 122.142C772.68 122.707 772.323 123.099 771.889 123.318C771.459 123.533 770.986 123.64 770.468 123.64ZM770.86 122.709C771.102 122.709 771.343 122.665 771.581 122.576C771.823 122.487 772.045 122.368 772.246 122.219C772.446 122.065 772.605 121.895 772.722 121.708C772.843 121.517 772.906 121.321 772.911 121.12V119.881L771.833 119.902C771.282 119.911 770.82 119.967 770.447 120.07C770.078 120.173 769.8 120.334 769.614 120.553C769.432 120.772 769.341 121.059 769.341 121.414C769.341 121.82 769.481 122.137 769.761 122.366C770.045 122.595 770.412 122.709 770.86 122.709ZM776.368 123.5V116.262H777.6V117.655C777.721 117.31 777.892 117.025 778.111 116.801C778.33 116.572 778.58 116.402 778.86 116.29C779.145 116.178 779.439 116.122 779.742 116.122C779.849 116.122 779.954 116.129 780.057 116.143C780.16 116.157 780.239 116.18 780.295 116.213V117.466C780.225 117.433 780.134 117.412 780.022 117.403C779.915 117.389 779.824 117.382 779.749 117.382C779.46 117.363 779.189 117.382 778.937 117.438C778.685 117.489 778.463 117.58 778.272 117.711C778.081 117.842 777.929 118.014 777.817 118.229C777.71 118.439 777.656 118.696 777.656 118.999V123.5H776.368ZM784.529 116.122C785.047 116.122 785.514 116.222 785.929 116.423C786.345 116.619 786.683 116.899 786.944 117.263C787.21 117.627 787.376 118.056 787.441 118.551H786.293C786.251 118.299 786.153 118.068 785.999 117.858C785.85 117.648 785.652 117.48 785.404 117.354C785.157 117.228 784.868 117.165 784.536 117.165C783.916 117.165 783.412 117.389 783.024 117.837C782.637 118.285 782.443 118.971 782.443 119.895C782.443 120.73 782.621 121.4 782.975 121.904C783.335 122.403 783.862 122.653 784.557 122.653C784.889 122.653 785.176 122.59 785.418 122.464C785.666 122.333 785.864 122.163 786.013 121.953C786.163 121.738 786.258 121.51 786.3 121.267H787.427C787.367 121.752 787.203 122.172 786.937 122.527C786.671 122.882 786.331 123.157 785.915 123.353C785.505 123.544 785.043 123.64 784.529 123.64C783.881 123.64 783.304 123.498 782.8 123.213C782.301 122.928 781.909 122.508 781.624 121.953C781.34 121.398 781.197 120.716 781.197 119.909C781.197 119.153 781.326 118.493 781.582 117.928C781.844 117.359 782.222 116.915 782.716 116.598C783.216 116.281 783.82 116.122 784.529 116.122ZM789.192 123.5V113.098H790.452V117.326C790.573 117.125 790.737 116.939 790.942 116.766C791.147 116.589 791.397 116.446 791.691 116.339C791.985 116.227 792.326 116.171 792.713 116.171C793.184 116.171 793.611 116.26 793.994 116.437C794.377 116.61 794.68 116.857 794.904 117.179C795.128 117.501 795.24 117.884 795.24 118.327V123.5H793.945V118.586C793.945 118.152 793.796 117.818 793.497 117.585C793.203 117.347 792.818 117.228 792.342 117.228C792.02 117.228 791.717 117.282 791.432 117.389C791.147 117.492 790.916 117.65 790.739 117.865C790.566 118.075 790.48 118.343 790.48 118.67V123.5H789.192Z" fill="#CBD5E1"/> +<g filter="url(#filter2_ddd_336_13394)"> +<rect x="689" y="136.5" width="20" height="20" rx="4" fill="white"/> +<path d="M704.154 145.407C704.289 145.002 704.335 144.573 704.291 144.149C704.246 143.724 704.111 143.314 703.896 142.946C703.576 142.389 703.087 141.948 702.501 141.687C701.914 141.426 701.26 141.358 700.632 141.492C700.276 141.095 699.821 140.799 699.314 140.634C698.807 140.469 698.265 140.44 697.743 140.55C697.221 140.66 696.737 140.906 696.34 141.262C695.943 141.619 695.647 142.073 695.481 142.581C695.063 142.666 694.668 142.84 694.322 143.091C693.977 143.342 693.689 143.663 693.477 144.034C693.154 144.59 693.016 145.234 693.083 145.874C693.15 146.513 693.418 147.115 693.85 147.592C693.715 147.997 693.668 148.426 693.712 148.85C693.756 149.275 693.89 149.685 694.106 150.053C694.426 150.61 694.915 151.051 695.502 151.312C696.089 151.574 696.743 151.642 697.372 151.507C697.655 151.826 698.003 152.081 698.393 152.255C698.782 152.429 699.205 152.517 699.632 152.515C700.275 152.516 700.902 152.312 701.422 151.933C701.942 151.554 702.328 151.019 702.525 150.407C702.943 150.321 703.338 150.147 703.684 149.896C704.029 149.645 704.317 149.324 704.529 148.953C704.848 148.398 704.984 147.756 704.917 147.119C704.849 146.483 704.583 145.883 704.154 145.407ZM699.632 151.728C699.105 151.728 698.594 151.544 698.19 151.206L698.261 151.166L700.656 149.783C700.716 149.748 700.765 149.698 700.8 149.638C700.834 149.578 700.853 149.511 700.853 149.441V146.064L701.866 146.65C701.871 146.653 701.875 146.656 701.878 146.661C701.881 146.665 701.884 146.671 701.885 146.676V149.475C701.883 150.072 701.646 150.644 701.223 151.066C700.801 151.489 700.229 151.726 699.632 151.728ZM694.789 149.66C694.525 149.203 694.43 148.668 694.521 148.149L694.592 148.192L696.99 149.574C697.049 149.609 697.117 149.627 697.185 149.627C697.254 149.627 697.322 149.609 697.381 149.574L700.31 147.886V149.055C700.31 149.061 700.308 149.067 700.305 149.072C700.302 149.078 700.298 149.082 700.293 149.086L697.867 150.485C697.349 150.783 696.734 150.864 696.157 150.709C695.58 150.554 695.088 150.177 694.789 149.66ZM694.158 144.442C694.424 143.983 694.844 143.633 695.344 143.453V146.299C695.343 146.368 695.361 146.436 695.395 146.495C695.429 146.555 695.478 146.604 695.538 146.638L698.453 148.32L697.44 148.905C697.435 148.908 697.429 148.91 697.422 148.91C697.416 148.91 697.41 148.908 697.405 148.905L694.983 147.509C694.467 147.209 694.09 146.717 693.935 146.14C693.78 145.563 693.86 144.948 694.158 144.43V144.442ZM702.477 146.375L699.553 144.677L700.564 144.094C700.569 144.091 700.575 144.089 700.581 144.089C700.588 144.089 700.594 144.091 700.599 144.094L703.02 145.493C703.391 145.706 703.693 146.021 703.891 146.4C704.089 146.778 704.175 147.206 704.14 147.632C704.104 148.058 703.948 148.465 703.69 148.805C703.432 149.146 703.082 149.406 702.681 149.555V146.709C702.679 146.641 702.659 146.574 702.624 146.515C702.588 146.457 702.537 146.408 702.477 146.375ZM703.485 144.86L703.414 144.817L701.021 143.422C700.962 143.387 700.894 143.369 700.824 143.369C700.755 143.369 700.687 143.387 700.628 143.422L697.701 145.111V143.942C697.701 143.936 697.702 143.93 697.704 143.925C697.707 143.919 697.711 143.914 697.715 143.911L700.137 142.514C700.508 142.3 700.932 142.197 701.36 142.215C701.788 142.234 702.202 142.374 702.553 142.619C702.904 142.864 703.178 143.204 703.344 143.599C703.509 143.994 703.558 144.428 703.485 144.85L703.485 144.86ZM697.149 146.932L696.136 146.349C696.131 146.346 696.127 146.342 696.123 146.337C696.12 146.332 696.118 146.326 696.117 146.32V143.529C696.117 143.101 696.24 142.682 696.47 142.321C696.7 141.959 697.028 141.671 697.416 141.49C697.804 141.308 698.235 141.241 698.66 141.295C699.085 141.35 699.485 141.524 699.814 141.798L699.743 141.838L697.348 143.221C697.288 143.256 697.239 143.306 697.204 143.366C697.17 143.425 697.151 143.493 697.151 143.562L697.149 146.932ZM697.699 145.747L699.003 144.995L700.31 145.747V147.25L699.008 148.002L697.701 147.25L697.699 145.747Z" fill="#0F172A"/> +</g> +<path d="M722.061 141.238C722.775 141.238 723.407 141.387 723.958 141.686C724.509 141.98 724.952 142.374 725.288 142.869C725.624 143.364 725.822 143.907 725.883 144.5H724.504C724.429 144.103 724.287 143.744 724.077 143.422C723.867 143.1 723.592 142.843 723.251 142.652C722.91 142.461 722.502 142.365 722.026 142.365C721.415 142.365 720.897 142.51 720.472 142.799C720.052 143.084 719.732 143.525 719.513 144.122C719.298 144.719 719.191 145.485 719.191 146.418C719.191 147.827 719.431 148.863 719.912 149.526C720.397 150.184 721.102 150.513 722.026 150.513C722.502 150.513 722.91 150.413 723.251 150.212C723.592 150.011 723.867 149.743 724.077 149.407C724.287 149.071 724.429 148.707 724.504 148.315H725.883C725.832 148.744 725.715 149.16 725.533 149.561C725.356 149.958 725.111 150.312 724.798 150.625C724.485 150.938 724.1 151.185 723.643 151.367C723.19 151.549 722.663 151.64 722.061 151.64C721.151 151.64 720.374 151.432 719.73 151.017C719.091 150.602 718.601 150.004 718.26 149.225C717.924 148.446 717.756 147.505 717.756 146.404C717.756 145.307 717.924 144.374 718.26 143.604C718.596 142.834 719.084 142.248 719.723 141.847C720.367 141.441 721.146 141.238 722.061 141.238ZM727.71 151.5V141.098H728.97V145.326C729.091 145.125 729.254 144.939 729.46 144.766C729.665 144.589 729.915 144.446 730.209 144.339C730.503 144.227 730.843 144.171 731.231 144.171C731.702 144.171 732.129 144.26 732.512 144.437C732.894 144.61 733.198 144.857 733.422 145.179C733.646 145.501 733.758 145.884 733.758 146.327V151.5H732.463V146.586C732.463 146.152 732.313 145.818 732.015 145.585C731.721 145.347 731.336 145.228 730.86 145.228C730.538 145.228 730.234 145.282 729.95 145.389C729.665 145.492 729.434 145.65 729.257 145.865C729.084 146.075 728.998 146.343 728.998 146.67V151.5H727.71ZM737.751 151.64C737.284 151.64 736.871 151.558 736.512 151.395C736.152 151.227 735.87 150.987 735.665 150.674C735.464 150.361 735.364 149.983 735.364 149.54C735.364 148.737 735.644 148.14 736.204 147.748C736.764 147.356 737.662 147.146 738.899 147.118L740.194 147.083V146.516C740.194 146.073 740.061 145.723 739.795 145.466C739.529 145.205 739.116 145.074 738.556 145.074C738.14 145.079 737.774 145.177 737.457 145.368C737.144 145.559 736.941 145.872 736.848 146.306H735.707C735.735 145.835 735.868 145.438 736.106 145.116C736.344 144.789 736.675 144.542 737.1 144.374C737.529 144.206 738.035 144.122 738.619 144.122C739.249 144.122 739.771 144.211 740.187 144.388C740.607 144.565 740.919 144.827 741.125 145.172C741.335 145.517 741.44 145.942 741.44 146.446V151.5H740.334L740.243 150.142C739.963 150.707 739.606 151.099 739.172 151.318C738.742 151.533 738.269 151.64 737.751 151.64ZM738.143 150.709C738.385 150.709 738.626 150.665 738.864 150.576C739.106 150.487 739.328 150.368 739.529 150.219C739.729 150.065 739.888 149.895 740.005 149.708C740.126 149.517 740.189 149.321 740.194 149.12V147.881L739.116 147.902C738.565 147.911 738.103 147.967 737.73 148.07C737.361 148.173 737.083 148.334 736.897 148.553C736.715 148.772 736.624 149.059 736.624 149.414C736.624 149.82 736.764 150.137 737.044 150.366C737.328 150.595 737.695 150.709 738.143 150.709ZM746.987 145.207H745.356V149.708C745.356 149.965 745.381 150.154 745.433 150.275C745.489 150.392 745.58 150.466 745.706 150.499C745.836 150.532 746.014 150.548 746.238 150.548H747.029V151.416C746.94 151.449 746.805 151.477 746.623 151.5C746.445 151.523 746.224 151.535 745.958 151.535C745.458 151.535 745.071 151.467 744.796 151.332C744.52 151.197 744.327 150.996 744.215 150.73C744.107 150.464 744.054 150.137 744.054 149.75V145.207H742.878V144.262H744.089L744.404 142.155H745.356V144.255H746.987V145.207ZM752.829 151.64C751.943 151.64 751.177 151.437 750.533 151.031C749.889 150.62 749.395 150.03 749.049 149.26C748.704 148.49 748.531 147.559 748.531 146.467C748.531 145.366 748.706 144.425 749.056 143.646C749.411 142.867 749.915 142.272 750.568 141.861C751.222 141.446 751.994 141.238 752.885 141.238C753.394 141.238 753.872 141.327 754.32 141.504C754.768 141.681 755.165 141.924 755.51 142.232C755.856 142.535 756.131 142.883 756.336 143.275C756.546 143.667 756.665 144.075 756.693 144.5H755.307C755.242 144.127 755.097 143.779 754.873 143.457C754.649 143.135 754.365 142.876 754.019 142.68C753.679 142.484 753.294 142.386 752.864 142.386C752.281 142.386 751.768 142.531 751.324 142.82C750.886 143.105 750.543 143.546 750.295 144.143C750.053 144.74 749.931 145.506 749.931 146.439C749.931 147.158 750.006 147.774 750.155 148.287C750.305 148.8 750.512 149.218 750.778 149.54C751.049 149.857 751.364 150.093 751.723 150.247C752.087 150.396 752.479 150.471 752.899 150.471C753.324 150.471 753.693 150.396 754.005 150.247C754.323 150.098 754.586 149.906 754.796 149.673C755.006 149.435 755.167 149.181 755.279 148.91C755.396 148.635 755.459 148.371 755.468 148.119L755.51 147.223H753.046V146.271L756.854 146.278V151.5H755.881V149.96C755.704 150.249 755.48 150.522 755.209 150.779C754.939 151.036 754.607 151.243 754.215 151.402C753.828 151.561 753.366 151.64 752.829 151.64ZM759.38 151.5V141.378H762.803C763.526 141.378 764.17 141.499 764.735 141.742C765.3 141.985 765.743 142.344 766.065 142.82C766.392 143.296 766.555 143.882 766.555 144.577C766.555 145.249 766.403 145.823 766.1 146.299C765.801 146.775 765.384 147.137 764.847 147.384C764.31 147.631 763.687 147.755 762.978 147.755H760.71V151.5H759.38ZM760.703 146.663H762.95C763.645 146.663 764.21 146.469 764.644 146.082C765.078 145.695 765.295 145.167 765.295 144.5C765.295 143.828 765.071 143.319 764.623 142.974C764.175 142.629 763.596 142.456 762.887 142.456H760.703V146.663ZM767.279 142.568V141.378H774.937V142.568H771.85V151.5H770.548V142.568H767.279ZM779.833 151.5V141.378H783.256C783.979 141.378 784.623 141.499 785.188 141.742C785.753 141.985 786.196 142.344 786.518 142.82C786.845 143.296 787.008 143.882 787.008 144.577C787.008 145.249 786.856 145.823 786.553 146.299C786.254 146.775 785.837 147.137 785.3 147.384C784.763 147.631 784.14 147.755 783.431 147.755H781.163V151.5H779.833ZM781.156 146.663H783.403C784.098 146.663 784.663 146.469 785.097 146.082C785.531 145.695 785.748 145.167 785.748 144.5C785.748 143.828 785.524 143.319 785.076 142.974C784.628 142.629 784.049 142.456 783.34 142.456H781.156V146.663ZM790.308 151.591C789.916 151.591 789.599 151.537 789.356 151.43C789.118 151.323 788.934 151.178 788.803 150.996C788.677 150.814 788.591 150.606 788.544 150.373C788.502 150.135 788.481 149.89 788.481 149.638V141.098H789.755V149.512C789.755 149.876 789.825 150.149 789.965 150.331C790.11 150.513 790.31 150.613 790.567 150.632L790.959 150.639V151.479C790.852 151.512 790.74 151.537 790.623 151.556C790.506 151.579 790.401 151.591 790.308 151.591ZM795.079 151.64C794.701 151.635 794.344 151.582 794.008 151.479C793.676 151.372 793.382 151.215 793.126 151.01C792.869 150.805 792.666 150.55 792.517 150.247C792.372 149.939 792.3 149.582 792.3 149.176V144.262H793.595V149.043C793.595 149.514 793.739 149.897 794.029 150.191C794.323 150.485 794.764 150.632 795.352 150.632C795.884 150.632 796.311 150.494 796.633 150.219C796.959 149.939 797.123 149.524 797.123 148.973V144.262H798.411V151.5H797.382L797.249 150.114C797.16 150.478 797.008 150.772 796.794 150.996C796.584 151.22 796.329 151.386 796.031 151.493C795.737 151.596 795.419 151.645 795.079 151.64ZM803.455 153.894C802.316 153.894 801.43 153.728 800.795 153.397C800.165 153.07 799.85 152.601 799.85 151.99C799.85 151.729 799.911 151.507 800.032 151.325C800.153 151.138 800.298 150.984 800.466 150.863C800.634 150.742 800.79 150.646 800.935 150.576C801.08 150.501 801.173 150.448 801.215 150.415C801.136 150.368 801.035 150.31 800.914 150.24C800.797 150.17 800.695 150.077 800.606 149.96C800.517 149.843 800.473 149.692 800.473 149.505C800.473 149.276 800.58 149.066 800.795 148.875C801.01 148.684 801.327 148.541 801.747 148.448C801.322 148.233 800.991 147.949 800.753 147.594C800.515 147.239 800.396 146.854 800.396 146.439C800.396 145.972 800.522 145.566 800.774 145.221C801.031 144.876 801.395 144.612 801.866 144.43C802.337 144.243 802.897 144.15 803.546 144.15C804.017 144.15 804.414 144.206 804.736 144.318C805.058 144.425 805.357 144.579 805.632 144.78C805.707 144.747 805.823 144.698 805.982 144.633C806.145 144.563 806.325 144.488 806.521 144.409C806.717 144.33 806.899 144.255 807.067 144.185C807.24 144.11 807.377 144.054 807.48 144.017L807.473 145.214L806.206 145.452C806.276 145.601 806.332 145.765 806.374 145.942C806.421 146.119 806.444 146.285 806.444 146.439C806.444 146.864 806.332 147.249 806.108 147.594C805.889 147.939 805.555 148.215 805.107 148.42C804.659 148.625 804.099 148.728 803.427 148.728C803.371 148.728 803.299 148.728 803.21 148.728C803.126 148.723 803.054 148.719 802.993 148.714C802.498 148.728 802.158 148.784 801.971 148.882C801.784 148.975 801.691 149.08 801.691 149.197C801.691 149.332 801.796 149.426 802.006 149.477C802.216 149.528 802.573 149.573 803.077 149.61C803.259 149.619 803.483 149.631 803.749 149.645C804.02 149.659 804.318 149.678 804.645 149.701C805.406 149.748 805.991 149.941 806.402 150.282C806.813 150.623 807.018 151.082 807.018 151.661C807.018 152.319 806.724 152.856 806.136 153.271C805.548 153.686 804.654 153.894 803.455 153.894ZM803.686 153.061C804.353 153.061 804.864 152.961 805.219 152.76C805.574 152.559 805.751 152.256 805.751 151.85C805.751 151.561 805.641 151.325 805.422 151.143C805.207 150.956 804.878 150.849 804.435 150.821L802.405 150.688C802.223 150.679 802.034 150.725 801.838 150.828C801.642 150.926 801.474 151.064 801.334 151.241C801.199 151.418 801.131 151.619 801.131 151.843C801.131 152.226 801.339 152.524 801.754 152.739C802.174 152.954 802.818 153.061 803.686 153.061ZM803.462 147.895C803.985 147.895 804.405 147.774 804.722 147.531C805.044 147.288 805.205 146.929 805.205 146.453C805.205 145.963 805.044 145.59 804.722 145.333C804.405 145.076 803.985 144.948 803.462 144.948C802.935 144.948 802.508 145.079 802.181 145.34C801.859 145.597 801.698 145.968 801.698 146.453C801.698 146.91 801.852 147.265 802.16 147.517C802.468 147.769 802.902 147.895 803.462 147.895ZM810.498 144.262V151.5H809.245V144.262H810.498ZM810.526 141.385V142.729H809.203V141.385H810.526ZM812.858 151.5V144.262H814.111V145.312C814.228 145.111 814.389 144.922 814.594 144.745C814.804 144.568 815.056 144.425 815.35 144.318C815.649 144.211 815.992 144.157 816.379 144.157C816.836 144.157 817.256 144.248 817.639 144.43C818.026 144.612 818.334 144.897 818.563 145.284C818.796 145.667 818.913 146.161 818.913 146.768V151.5H817.618V146.894C817.618 146.329 817.469 145.912 817.17 145.641C816.876 145.366 816.493 145.228 816.022 145.228C815.695 145.228 815.39 145.282 815.105 145.389C814.82 145.492 814.589 145.65 814.412 145.865C814.235 146.075 814.146 146.341 814.146 146.663V151.5H812.858ZM823.398 151.64C822.894 151.64 822.425 151.563 821.991 151.409C821.561 151.25 821.204 151.005 820.92 150.674C820.635 150.338 820.455 149.911 820.381 149.393H821.557C821.622 149.678 821.739 149.916 821.907 150.107C822.079 150.294 822.294 150.436 822.551 150.534C822.807 150.627 823.087 150.674 823.391 150.674C823.876 150.674 824.268 150.583 824.567 150.401C824.865 150.219 825.015 149.939 825.015 149.561C825.015 149.295 824.935 149.083 824.777 148.924C824.623 148.761 824.382 148.642 824.056 148.567L822.558 148.196C821.974 148.056 821.508 147.837 821.158 147.538C820.808 147.239 820.633 146.822 820.633 146.285C820.628 145.86 820.728 145.487 820.934 145.165C821.139 144.843 821.447 144.589 821.858 144.402C822.268 144.215 822.779 144.122 823.391 144.122C824.175 144.122 824.807 144.299 825.288 144.654C825.773 145.004 826.025 145.52 826.044 146.201H824.903C824.856 145.856 824.7 145.583 824.434 145.382C824.172 145.177 823.82 145.074 823.377 145.074C822.915 145.074 822.539 145.167 822.25 145.354C821.96 145.536 821.816 145.821 821.816 146.208C821.816 146.465 821.918 146.668 822.124 146.817C822.329 146.962 822.632 147.083 823.034 147.181L824.483 147.545C824.819 147.634 825.094 147.75 825.309 147.895C825.528 148.04 825.701 148.201 825.827 148.378C825.957 148.555 826.048 148.742 826.1 148.938C826.156 149.129 826.184 149.314 826.184 149.491C826.184 149.934 826.072 150.317 825.848 150.639C825.628 150.956 825.311 151.204 824.896 151.381C824.485 151.554 823.986 151.64 823.398 151.64Z" fill="#CBD5E1"/> +<g filter="url(#filter3_ddd_336_13394)"> +<rect x="689" y="164.5" width="20" height="20" rx="4" fill="white"/> +<rect x="693" y="168.583" width="12" height="11.8344" fill="url(#pattern1)"/> +</g> +<path d="M721.774 179.64C721.298 179.64 720.836 179.582 720.388 179.465C719.94 179.344 719.534 179.164 719.17 178.926C718.806 178.688 718.503 178.394 718.26 178.044C718.022 177.689 717.873 177.279 717.812 176.812H719.17C719.254 177.195 719.42 177.517 719.667 177.778C719.919 178.039 720.225 178.235 720.584 178.366C720.948 178.497 721.342 178.562 721.767 178.562C722.243 178.562 722.668 178.497 723.041 178.366C723.419 178.231 723.715 178.039 723.93 177.792C724.149 177.545 724.259 177.248 724.259 176.903C724.259 176.6 724.189 176.336 724.049 176.112C723.914 175.888 723.715 175.701 723.454 175.552C723.193 175.403 722.875 175.286 722.502 175.202L720.514 174.74C719.767 174.577 719.182 174.285 718.757 173.865C718.337 173.445 718.122 172.89 718.113 172.199C718.113 171.616 718.267 171.102 718.575 170.659C718.888 170.216 719.315 169.87 719.856 169.623C720.402 169.376 721.025 169.252 721.725 169.252C722.532 169.252 723.211 169.39 723.762 169.665C724.313 169.94 724.728 170.302 725.008 170.75C725.288 171.198 725.43 171.676 725.435 172.185H724.098C724.056 171.728 723.921 171.366 723.692 171.1C723.468 170.829 723.186 170.638 722.845 170.526C722.504 170.409 722.133 170.351 721.732 170.351C721.443 170.351 721.163 170.388 720.892 170.463C720.626 170.533 720.386 170.64 720.171 170.785C719.961 170.93 719.793 171.109 719.667 171.324C719.541 171.539 719.478 171.788 719.478 172.073C719.478 172.437 719.602 172.733 719.849 172.962C720.101 173.191 720.556 173.387 721.214 173.55L723.139 174.012C723.643 174.119 724.054 174.278 724.371 174.488C724.693 174.698 724.94 174.936 725.113 175.202C725.29 175.468 725.412 175.746 725.477 176.035C725.547 176.324 725.582 176.609 725.582 176.889C725.582 177.398 725.43 177.862 725.127 178.282C724.824 178.697 724.387 179.029 723.818 179.276C723.253 179.519 722.572 179.64 721.774 179.64ZM728.429 176.182C728.429 176.639 728.503 177.055 728.653 177.428C728.807 177.797 729.038 178.091 729.346 178.31C729.658 178.529 730.048 178.639 730.515 178.639C730.977 178.639 731.373 178.532 731.705 178.317C732.041 178.102 732.255 177.792 732.349 177.386H733.574C733.485 177.876 733.289 178.289 732.986 178.625C732.682 178.961 732.316 179.215 731.887 179.388C731.457 179.556 731.009 179.64 730.543 179.64C729.866 179.64 729.271 179.493 728.758 179.199C728.244 178.905 727.843 178.483 727.554 177.932C727.264 177.381 727.12 176.721 727.12 175.951C727.12 175.19 727.25 174.523 727.512 173.949C727.773 173.375 728.151 172.927 728.646 172.605C729.145 172.283 729.745 172.122 730.445 172.122C731.126 172.122 731.702 172.269 732.174 172.563C732.645 172.857 733.002 173.272 733.245 173.809C733.492 174.341 733.616 174.973 733.616 175.706V176.182H728.429ZM728.436 175.307H732.37C732.37 174.892 732.3 174.518 732.16 174.187C732.02 173.851 731.805 173.587 731.516 173.396C731.231 173.2 730.872 173.102 730.438 173.102C729.985 173.102 729.609 173.212 729.311 173.431C729.017 173.646 728.795 173.923 728.646 174.264C728.501 174.6 728.431 174.948 728.436 175.307ZM735.393 179.5V172.262H736.625V173.655C736.747 173.31 736.917 173.025 737.136 172.801C737.356 172.572 737.605 172.402 737.885 172.29C738.17 172.178 738.464 172.122 738.767 172.122C738.875 172.122 738.98 172.129 739.082 172.143C739.185 172.157 739.264 172.18 739.32 172.213V173.466C739.25 173.433 739.159 173.412 739.047 173.403C738.94 173.389 738.849 173.382 738.774 173.382C738.485 173.363 738.214 173.382 737.962 173.438C737.71 173.489 737.489 173.58 737.297 173.711C737.106 173.842 736.954 174.014 736.842 174.229C736.735 174.439 736.681 174.696 736.681 174.999V179.5H735.393ZM740.821 181.754V172.262H742.109L742.123 173.522C742.207 173.373 742.314 173.216 742.445 173.053C742.58 172.89 742.744 172.738 742.935 172.598C743.131 172.458 743.357 172.344 743.614 172.255C743.875 172.166 744.172 172.122 744.503 172.122C745.082 172.122 745.59 172.255 746.029 172.521C746.472 172.782 746.818 173.184 747.065 173.725C747.312 174.266 747.436 174.952 747.436 175.783C747.436 176.614 747.315 177.316 747.072 177.89C746.834 178.459 746.489 178.893 746.036 179.192C745.583 179.491 745.037 179.64 744.398 179.64C744.076 179.64 743.789 179.593 743.537 179.5C743.29 179.411 743.073 179.295 742.886 179.15C742.704 179.001 742.55 178.842 742.424 178.674C742.303 178.506 742.202 178.345 742.123 178.191V181.754H740.821ZM744.153 178.632C744.75 178.632 745.238 178.406 745.616 177.953C745.994 177.496 746.183 176.786 746.183 175.825C746.183 174.999 746.01 174.343 745.665 173.858C745.32 173.373 744.816 173.13 744.153 173.13C743.481 173.13 742.972 173.38 742.627 173.879C742.282 174.374 742.109 175.022 742.109 175.825C742.109 176.343 742.186 176.814 742.34 177.239C742.494 177.664 742.723 178.002 743.026 178.254C743.329 178.506 743.705 178.632 744.153 178.632ZM748.644 179.5L752.305 169.378H753.768L757.429 179.5H756.106L755.112 176.728H750.975L749.96 179.5H748.644ZM751.304 175.538H754.769L753.047 170.659L751.304 175.538ZM759.339 179.5V169.378H762.762C763.485 169.378 764.129 169.499 764.694 169.742C765.259 169.985 765.702 170.344 766.024 170.82C766.351 171.296 766.514 171.882 766.514 172.577C766.514 173.249 766.362 173.823 766.059 174.299C765.76 174.775 765.343 175.137 764.806 175.384C764.269 175.631 763.646 175.755 762.937 175.755H760.669V179.5H759.339ZM760.662 174.663H762.909C763.604 174.663 764.169 174.469 764.603 174.082C765.037 173.695 765.254 173.167 765.254 172.5C765.254 171.828 765.03 171.319 764.582 170.974C764.134 170.629 763.555 170.456 762.846 170.456H760.662V174.663ZM768.253 169.378H769.548V179.5H768.253V169.378Z" fill="#CBD5E1"/> +<g filter="url(#filter4_ddd_336_13394)"> +<rect x="689" y="192.5" width="20" height="20" rx="4" fill="white"/> +<rect x="693" y="196.5" width="12" height="12" fill="url(#pattern2)"/> +</g> +<path d="M722.054 207.64C721.167 207.64 720.402 207.437 719.758 207.031C719.114 206.62 718.619 206.03 718.274 205.26C717.929 204.49 717.756 203.559 717.756 202.467C717.756 201.366 717.931 200.425 718.281 199.646C718.636 198.867 719.14 198.272 719.793 197.861C720.446 197.446 721.219 197.238 722.11 197.238C722.619 197.238 723.097 197.327 723.545 197.504C723.993 197.681 724.39 197.924 724.735 198.232C725.08 198.535 725.356 198.883 725.561 199.275C725.771 199.667 725.89 200.075 725.918 200.5H724.532C724.467 200.127 724.322 199.779 724.098 199.457C723.874 199.135 723.589 198.876 723.244 198.68C722.903 198.484 722.518 198.386 722.089 198.386C721.506 198.386 720.992 198.531 720.549 198.82C720.11 199.105 719.767 199.546 719.52 200.143C719.277 200.74 719.156 201.506 719.156 202.439C719.156 203.158 719.231 203.774 719.38 204.287C719.529 204.8 719.737 205.218 720.003 205.54C720.274 205.857 720.589 206.093 720.948 206.247C721.312 206.396 721.704 206.471 722.124 206.471C722.549 206.471 722.917 206.396 723.23 206.247C723.547 206.098 723.811 205.906 724.021 205.673C724.231 205.435 724.392 205.181 724.504 204.91C724.621 204.635 724.684 204.371 724.693 204.119L724.735 203.223H722.271V202.271L726.079 202.278V207.5H725.106V205.96C724.929 206.249 724.705 206.522 724.434 206.779C724.163 207.036 723.832 207.243 723.44 207.402C723.053 207.561 722.591 207.64 722.054 207.64ZM731.216 207.64C730.548 207.64 729.965 207.498 729.466 207.213C728.966 206.924 728.579 206.501 728.304 205.946C728.028 205.391 727.891 204.712 727.891 203.909C727.891 203.153 728.019 202.49 728.276 201.921C728.532 201.352 728.908 200.911 729.403 200.598C729.902 200.281 730.506 200.122 731.216 200.122C731.883 200.122 732.462 200.269 732.952 200.563C733.446 200.852 733.829 201.279 734.1 201.844C734.37 202.409 734.506 203.097 734.506 203.909C734.506 204.646 734.38 205.295 734.128 205.855C733.88 206.415 733.512 206.854 733.022 207.171C732.536 207.484 731.934 207.64 731.216 207.64ZM731.216 206.618C731.65 206.618 732.016 206.508 732.315 206.289C732.613 206.07 732.84 205.755 732.994 205.344C733.148 204.933 733.225 204.446 733.225 203.881C733.225 203.363 733.157 202.899 733.022 202.488C732.886 202.073 732.669 201.744 732.371 201.501C732.077 201.258 731.692 201.137 731.216 201.137C730.777 201.137 730.406 201.247 730.103 201.466C729.799 201.681 729.568 201.993 729.41 202.404C729.256 202.815 729.179 203.307 729.179 203.881C729.179 204.39 729.249 204.852 729.389 205.267C729.529 205.682 729.748 206.011 730.047 206.254C730.345 206.497 730.735 206.618 731.216 206.618ZM739.077 207.64C738.41 207.64 737.826 207.498 737.327 207.213C736.828 206.924 736.44 206.501 736.165 205.946C735.89 205.391 735.752 204.712 735.752 203.909C735.752 203.153 735.88 202.49 736.137 201.921C736.394 201.352 736.769 200.911 737.264 200.598C737.763 200.281 738.368 200.122 739.077 200.122C739.744 200.122 740.323 200.269 740.813 200.563C741.308 200.852 741.69 201.279 741.961 201.844C742.232 202.409 742.367 203.097 742.367 203.909C742.367 204.646 742.241 205.295 741.989 205.855C741.742 206.415 741.373 206.854 740.883 207.171C740.398 207.484 739.796 207.64 739.077 207.64ZM739.077 206.618C739.511 206.618 739.877 206.508 740.176 206.289C740.475 206.07 740.701 205.755 740.855 205.344C741.009 204.933 741.086 204.446 741.086 203.881C741.086 203.363 741.018 202.899 740.883 202.488C740.748 202.073 740.531 201.744 740.232 201.501C739.938 201.258 739.553 201.137 739.077 201.137C738.638 201.137 738.267 201.247 737.964 201.466C737.661 201.681 737.43 201.993 737.271 202.404C737.117 202.815 737.04 203.307 737.04 203.881C737.04 204.39 737.11 204.852 737.25 205.267C737.39 205.682 737.609 206.011 737.908 206.254C738.207 206.497 738.596 206.618 739.077 206.618ZM747.059 209.894C745.92 209.894 745.033 209.728 744.399 209.397C743.769 209.07 743.454 208.601 743.454 207.99C743.454 207.729 743.514 207.507 743.636 207.325C743.757 207.138 743.902 206.984 744.07 206.863C744.238 206.742 744.394 206.646 744.539 206.576C744.683 206.501 744.777 206.448 744.819 206.415C744.739 206.368 744.639 206.31 744.518 206.24C744.401 206.17 744.298 206.077 744.21 205.96C744.121 205.843 744.077 205.692 744.077 205.505C744.077 205.276 744.184 205.066 744.399 204.875C744.613 204.684 744.931 204.541 745.351 204.448C744.926 204.233 744.595 203.949 744.357 203.594C744.119 203.239 744 202.854 744 202.439C744 201.972 744.126 201.566 744.378 201.221C744.634 200.876 744.998 200.612 745.47 200.43C745.941 200.243 746.501 200.15 747.15 200.15C747.621 200.15 748.018 200.206 748.34 200.318C748.662 200.425 748.96 200.579 749.236 200.78C749.31 200.747 749.427 200.698 749.586 200.633C749.749 200.563 749.929 200.488 750.125 200.409C750.321 200.33 750.503 200.255 750.671 200.185C750.843 200.11 750.981 200.054 751.084 200.017L751.077 201.214L749.81 201.452C749.88 201.601 749.936 201.765 749.978 201.942C750.024 202.119 750.048 202.285 750.048 202.439C750.048 202.864 749.936 203.249 749.712 203.594C749.492 203.939 749.159 204.215 748.711 204.42C748.263 204.625 747.703 204.728 747.031 204.728C746.975 204.728 746.902 204.728 746.814 204.728C746.73 204.723 746.657 204.719 746.597 204.714C746.102 204.728 745.761 204.784 745.575 204.882C745.388 204.975 745.295 205.08 745.295 205.197C745.295 205.332 745.4 205.426 745.61 205.477C745.82 205.528 746.177 205.573 746.681 205.61C746.863 205.619 747.087 205.631 747.353 205.645C747.623 205.659 747.922 205.678 748.249 205.701C749.009 205.748 749.595 205.941 750.006 206.282C750.416 206.623 750.622 207.082 750.622 207.661C750.622 208.319 750.328 208.856 749.74 209.271C749.152 209.686 748.258 209.894 747.059 209.894ZM747.29 209.061C747.957 209.061 748.468 208.961 748.823 208.76C749.177 208.559 749.355 208.256 749.355 207.85C749.355 207.561 749.245 207.325 749.026 207.143C748.811 206.956 748.482 206.849 748.039 206.821L746.009 206.688C745.827 206.679 745.638 206.725 745.442 206.828C745.246 206.926 745.078 207.064 744.938 207.241C744.802 207.418 744.735 207.619 744.735 207.843C744.735 208.226 744.942 208.524 745.358 208.739C745.778 208.954 746.422 209.061 747.29 209.061ZM747.066 203.895C747.588 203.895 748.008 203.774 748.326 203.531C748.648 203.288 748.809 202.929 748.809 202.453C748.809 201.963 748.648 201.59 748.326 201.333C748.008 201.076 747.588 200.948 747.066 200.948C746.538 200.948 746.111 201.079 745.785 201.34C745.463 201.597 745.302 201.968 745.302 202.453C745.302 202.91 745.456 203.265 745.764 203.517C746.072 203.769 746.506 203.895 747.066 203.895ZM754.529 207.591C754.137 207.591 753.82 207.537 753.577 207.43C753.339 207.323 753.155 207.178 753.024 206.996C752.898 206.814 752.812 206.606 752.765 206.373C752.723 206.135 752.702 205.89 752.702 205.638V197.098H753.976V205.512C753.976 205.876 754.046 206.149 754.186 206.331C754.331 206.513 754.531 206.613 754.788 206.632L755.18 206.639V207.479C755.073 207.512 754.961 207.537 754.844 207.556C754.727 207.579 754.622 207.591 754.529 207.591ZM757.686 204.182C757.686 204.639 757.761 205.055 757.91 205.428C758.064 205.797 758.295 206.091 758.603 206.31C758.916 206.529 759.306 206.639 759.772 206.639C760.234 206.639 760.631 206.532 760.962 206.317C761.298 206.102 761.513 205.792 761.606 205.386H762.831C762.743 205.876 762.547 206.289 762.243 206.625C761.94 206.961 761.574 207.215 761.144 207.388C760.715 207.556 760.267 207.64 759.8 207.64C759.124 207.64 758.529 207.493 758.015 207.199C757.502 206.905 757.101 206.483 756.811 205.932C756.522 205.381 756.377 204.721 756.377 203.951C756.377 203.19 756.508 202.523 756.769 201.949C757.031 201.375 757.409 200.927 757.903 200.605C758.403 200.283 759.002 200.122 759.702 200.122C760.384 200.122 760.96 200.269 761.431 200.563C761.903 200.857 762.26 201.272 762.502 201.809C762.75 202.341 762.873 202.973 762.873 203.706V204.182H757.686ZM757.693 203.307H761.627C761.627 202.892 761.557 202.518 761.417 202.187C761.277 201.851 761.063 201.587 760.773 201.396C760.489 201.2 760.129 201.102 759.695 201.102C759.243 201.102 758.867 201.212 758.568 201.431C758.274 201.646 758.053 201.923 757.903 202.264C757.759 202.6 757.689 202.948 757.693 203.307Z" fill="#CBD5E1"/> +<g filter="url(#filter5_ddd_336_13394)"> +<rect x="689" y="220.5" width="20" height="20" rx="4" fill="white"/> +</g> +<rect x="693.268" y="224.815" width="12" height="11.4565" fill="url(#pattern3)"/> +<path d="M725.092 230.831H719.632V235.5H718.337V225.378H719.632V229.69H725.092V225.378H726.387V235.5H725.092V230.831ZM731.45 235.64C731.072 235.635 730.715 235.582 730.379 235.479C730.047 235.372 729.753 235.215 729.497 235.01C729.24 234.805 729.037 234.55 728.888 234.247C728.743 233.939 728.671 233.582 728.671 233.176V228.262H729.966V233.043C729.966 233.514 730.11 233.897 730.4 234.191C730.694 234.485 731.135 234.632 731.723 234.632C732.255 234.632 732.682 234.494 733.004 234.219C733.33 233.939 733.494 233.524 733.494 232.973V228.262H734.782V235.5H733.753L733.62 234.114C733.531 234.478 733.379 234.772 733.165 234.996C732.955 235.22 732.7 235.386 732.402 235.493C732.108 235.596 731.79 235.645 731.45 235.64ZM739.826 237.894C738.687 237.894 737.801 237.728 737.166 237.397C736.536 237.07 736.221 236.601 736.221 235.99C736.221 235.729 736.282 235.507 736.403 235.325C736.524 235.138 736.669 234.984 736.837 234.863C737.005 234.742 737.161 234.646 737.306 234.576C737.451 234.501 737.544 234.448 737.586 234.415C737.507 234.368 737.406 234.31 737.285 234.24C737.168 234.17 737.066 234.077 736.977 233.96C736.888 233.843 736.844 233.692 736.844 233.505C736.844 233.276 736.951 233.066 737.166 232.875C737.381 232.684 737.698 232.541 738.118 232.448C737.693 232.233 737.362 231.949 737.124 231.594C736.886 231.239 736.767 230.854 736.767 230.439C736.767 229.972 736.893 229.566 737.145 229.221C737.402 228.876 737.766 228.612 738.237 228.43C738.708 228.243 739.268 228.15 739.917 228.15C740.388 228.15 740.785 228.206 741.107 228.318C741.429 228.425 741.728 228.579 742.003 228.78C742.078 228.747 742.194 228.698 742.353 228.633C742.516 228.563 742.696 228.488 742.892 228.409C743.088 228.33 743.27 228.255 743.438 228.185C743.611 228.11 743.748 228.054 743.851 228.017L743.844 229.214L742.577 229.452C742.647 229.601 742.703 229.765 742.745 229.942C742.792 230.119 742.815 230.285 742.815 230.439C742.815 230.864 742.703 231.249 742.479 231.594C742.26 231.939 741.926 232.215 741.478 232.42C741.03 232.625 740.47 232.728 739.798 232.728C739.742 232.728 739.67 232.728 739.581 232.728C739.497 232.723 739.425 232.719 739.364 232.714C738.869 232.728 738.529 232.784 738.342 232.882C738.155 232.975 738.062 233.08 738.062 233.197C738.062 233.332 738.167 233.426 738.377 233.477C738.587 233.528 738.944 233.573 739.448 233.61C739.63 233.619 739.854 233.631 740.12 233.645C740.391 233.659 740.689 233.678 741.016 233.701C741.777 233.748 742.362 233.941 742.773 234.282C743.184 234.623 743.389 235.082 743.389 235.661C743.389 236.319 743.095 236.856 742.507 237.271C741.919 237.686 741.025 237.894 739.826 237.894ZM740.057 237.061C740.724 237.061 741.235 236.961 741.59 236.76C741.945 236.559 742.122 236.256 742.122 235.85C742.122 235.561 742.012 235.325 741.793 235.143C741.578 234.956 741.249 234.849 740.806 234.821L738.776 234.688C738.594 234.679 738.405 234.725 738.209 234.828C738.013 234.926 737.845 235.064 737.705 235.241C737.57 235.418 737.502 235.619 737.502 235.843C737.502 236.226 737.71 236.524 738.125 236.739C738.545 236.954 739.189 237.061 740.057 237.061ZM739.833 231.895C740.356 231.895 740.776 231.774 741.093 231.531C741.415 231.288 741.576 230.929 741.576 230.453C741.576 229.963 741.415 229.59 741.093 229.333C740.776 229.076 740.356 228.948 739.833 228.948C739.306 228.948 738.879 229.079 738.552 229.34C738.23 229.597 738.069 229.968 738.069 230.453C738.069 230.91 738.223 231.265 738.531 231.517C738.839 231.769 739.273 231.895 739.833 231.895ZM748.439 237.894C747.301 237.894 746.414 237.728 745.779 237.397C745.149 237.07 744.834 236.601 744.834 235.99C744.834 235.729 744.895 235.507 745.016 235.325C745.138 235.138 745.282 234.984 745.45 234.863C745.618 234.742 745.775 234.646 745.919 234.576C746.064 234.501 746.157 234.448 746.199 234.415C746.12 234.368 746.02 234.31 745.898 234.24C745.782 234.17 745.679 234.077 745.59 233.96C745.502 233.843 745.457 233.692 745.457 233.505C745.457 233.276 745.565 233.066 745.779 232.875C745.994 232.684 746.311 232.541 746.731 232.448C746.307 232.233 745.975 231.949 745.737 231.594C745.499 231.239 745.38 230.854 745.38 230.439C745.38 229.972 745.506 229.566 745.758 229.221C746.015 228.876 746.379 228.612 746.85 228.43C747.322 228.243 747.882 228.15 748.53 228.15C749.002 228.15 749.398 228.206 749.72 228.318C750.042 228.425 750.341 228.579 750.616 228.78C750.691 228.747 750.808 228.698 750.966 228.633C751.13 228.563 751.309 228.488 751.505 228.409C751.701 228.33 751.883 228.255 752.051 228.185C752.224 228.11 752.362 228.054 752.464 228.017L752.457 229.214L751.19 229.452C751.26 229.601 751.316 229.765 751.358 229.942C751.405 230.119 751.428 230.285 751.428 230.439C751.428 230.864 751.316 231.249 751.092 231.594C750.873 231.939 750.539 232.215 750.091 232.42C749.643 232.625 749.083 232.728 748.411 232.728C748.355 232.728 748.283 232.728 748.194 232.728C748.11 232.723 748.038 232.719 747.977 232.714C747.483 232.728 747.142 232.784 746.955 232.882C746.769 232.975 746.675 233.08 746.675 233.197C746.675 233.332 746.78 233.426 746.99 233.477C747.2 233.528 747.557 233.573 748.061 233.61C748.243 233.619 748.467 233.631 748.733 233.645C749.004 233.659 749.303 233.678 749.629 233.701C750.39 233.748 750.976 233.941 751.386 234.282C751.797 234.623 752.002 235.082 752.002 235.661C752.002 236.319 751.708 236.856 751.12 237.271C750.532 237.686 749.639 237.894 748.439 237.894ZM748.67 237.061C749.338 237.061 749.849 236.961 750.203 236.76C750.558 236.559 750.735 236.256 750.735 235.85C750.735 235.561 750.626 235.325 750.406 235.143C750.192 234.956 749.863 234.849 749.419 234.821L747.389 234.688C747.207 234.679 747.018 234.725 746.822 234.828C746.626 234.926 746.458 235.064 746.318 235.241C746.183 235.418 746.115 235.619 746.115 235.843C746.115 236.226 746.323 236.524 746.738 236.739C747.158 236.954 747.802 237.061 748.67 237.061ZM748.446 231.895C748.969 231.895 749.389 231.774 749.706 231.531C750.028 231.288 750.189 230.929 750.189 230.453C750.189 229.963 750.028 229.59 749.706 229.333C749.389 229.076 748.969 228.948 748.446 228.948C747.919 228.948 747.492 229.079 747.165 229.34C746.843 229.597 746.682 229.968 746.682 230.453C746.682 230.91 746.836 231.265 747.144 231.517C747.452 231.769 747.886 231.895 748.446 231.895ZM755.483 228.262V235.5H754.23V228.262H755.483ZM755.511 225.385V226.729H754.188V225.385H755.511ZM757.842 235.5V228.262H759.095V229.312C759.212 229.111 759.373 228.922 759.578 228.745C759.788 228.568 760.04 228.425 760.334 228.318C760.633 228.211 760.976 228.157 761.363 228.157C761.821 228.157 762.241 228.248 762.623 228.43C763.011 228.612 763.319 228.897 763.547 229.284C763.781 229.667 763.897 230.161 763.897 230.768V235.5H762.602V230.894C762.602 230.329 762.453 229.912 762.154 229.641C761.86 229.366 761.478 229.228 761.006 229.228C760.68 229.228 760.374 229.282 760.089 229.389C759.805 229.492 759.574 229.65 759.396 229.865C759.219 230.075 759.13 230.341 759.13 230.663V235.5H757.842ZM768.975 237.894C767.836 237.894 766.949 237.728 766.315 237.397C765.685 237.07 765.37 236.601 765.37 235.99C765.37 235.729 765.43 235.507 765.552 235.325C765.673 235.138 765.818 234.984 765.986 234.863C766.154 234.742 766.31 234.646 766.455 234.576C766.599 234.501 766.693 234.448 766.735 234.415C766.655 234.368 766.555 234.31 766.434 234.24C766.317 234.17 766.214 234.077 766.126 233.96C766.037 233.843 765.993 233.692 765.993 233.505C765.993 233.276 766.1 233.066 766.315 232.875C766.529 232.684 766.847 232.541 767.267 232.448C766.842 232.233 766.511 231.949 766.273 231.594C766.035 231.239 765.916 230.854 765.916 230.439C765.916 229.972 766.042 229.566 766.294 229.221C766.55 228.876 766.914 228.612 767.386 228.43C767.857 228.243 768.417 228.15 769.066 228.15C769.537 228.15 769.934 228.206 770.256 228.318C770.578 228.425 770.876 228.579 771.152 228.78C771.226 228.747 771.343 228.698 771.502 228.633C771.665 228.563 771.845 228.488 772.041 228.409C772.237 228.33 772.419 228.255 772.587 228.185C772.759 228.11 772.897 228.054 773 228.017L772.993 229.214L771.726 229.452C771.796 229.601 771.852 229.765 771.894 229.942C771.94 230.119 771.964 230.285 771.964 230.439C771.964 230.864 771.852 231.249 771.628 231.594C771.408 231.939 771.075 232.215 770.627 232.42C770.179 232.625 769.619 232.728 768.947 232.728C768.891 232.728 768.818 232.728 768.73 232.728C768.646 232.723 768.573 232.719 768.513 232.714C768.018 232.728 767.677 232.784 767.491 232.882C767.304 232.975 767.211 233.08 767.211 233.197C767.211 233.332 767.316 233.426 767.526 233.477C767.736 233.528 768.093 233.573 768.597 233.61C768.779 233.619 769.003 233.631 769.269 233.645C769.539 233.659 769.838 233.678 770.165 233.701C770.925 233.748 771.511 233.941 771.922 234.282C772.332 234.623 772.538 235.082 772.538 235.661C772.538 236.319 772.244 236.856 771.656 237.271C771.068 237.686 770.174 237.894 768.975 237.894ZM769.206 237.061C769.873 237.061 770.384 236.961 770.739 236.76C771.093 236.559 771.271 236.256 771.271 235.85C771.271 235.561 771.161 235.325 770.942 235.143C770.727 234.956 770.398 234.849 769.955 234.821L767.925 234.688C767.743 234.679 767.554 234.725 767.358 234.828C767.162 234.926 766.994 235.064 766.854 235.241C766.718 235.418 766.651 235.619 766.651 235.843C766.651 236.226 766.858 236.524 767.274 236.739C767.694 236.954 768.338 237.061 769.206 237.061ZM768.982 231.895C769.504 231.895 769.924 231.774 770.242 231.531C770.564 231.288 770.725 230.929 770.725 230.453C770.725 229.963 770.564 229.59 770.242 229.333C769.924 229.076 769.504 228.948 768.982 228.948C768.454 228.948 768.027 229.079 767.701 229.34C767.379 229.597 767.218 229.968 767.218 230.453C767.218 230.91 767.372 231.265 767.68 231.517C767.988 231.769 768.422 231.895 768.982 231.895ZM778.11 225.378H784.417L784.41 226.512H779.44V229.858H783.906V230.978H779.44V235.5H778.11V225.378ZM788.022 235.64C787.556 235.64 787.143 235.558 786.783 235.395C786.424 235.227 786.142 234.987 785.936 234.674C785.736 234.361 785.635 233.983 785.635 233.54C785.635 232.737 785.915 232.14 786.475 231.748C787.035 231.356 787.934 231.146 789.17 231.118L790.465 231.083V230.516C790.465 230.073 790.332 229.723 790.066 229.466C789.8 229.205 789.387 229.074 788.827 229.074C788.412 229.079 788.046 229.177 787.728 229.368C787.416 229.559 787.213 229.872 787.119 230.306H785.978C786.006 229.835 786.139 229.438 786.377 229.116C786.615 228.789 786.947 228.542 787.371 228.374C787.801 228.206 788.307 228.122 788.89 228.122C789.52 228.122 790.043 228.211 790.458 228.388C790.878 228.565 791.191 228.827 791.396 229.172C791.606 229.517 791.711 229.942 791.711 230.446V235.5H790.605L790.514 234.142C790.234 234.707 789.877 235.099 789.443 235.318C789.014 235.533 788.54 235.64 788.022 235.64ZM788.414 234.709C788.657 234.709 788.897 234.665 789.135 234.576C789.378 234.487 789.6 234.368 789.8 234.219C790.001 234.065 790.16 233.895 790.276 233.708C790.398 233.517 790.461 233.321 790.465 233.12V231.881L789.387 231.902C788.837 231.911 788.375 231.967 788.001 232.07C787.633 232.173 787.355 232.334 787.168 232.553C786.986 232.772 786.895 233.059 786.895 233.414C786.895 233.82 787.035 234.137 787.315 234.366C787.6 234.595 787.966 234.709 788.414 234.709ZM796.807 228.122C797.325 228.122 797.791 228.222 798.207 228.423C798.622 228.619 798.96 228.899 799.222 229.263C799.488 229.627 799.653 230.056 799.719 230.551H798.571C798.529 230.299 798.431 230.068 798.277 229.858C798.127 229.648 797.929 229.48 797.682 229.354C797.434 229.228 797.145 229.165 796.814 229.165C796.193 229.165 795.689 229.389 795.302 229.837C794.914 230.285 794.721 230.971 794.721 231.895C794.721 232.73 794.898 233.4 795.253 233.904C795.612 234.403 796.139 234.653 796.835 234.653C797.166 234.653 797.453 234.59 797.696 234.464C797.943 234.333 798.141 234.163 798.291 233.953C798.44 233.738 798.536 233.51 798.578 233.267H799.705C799.644 233.752 799.481 234.172 799.215 234.527C798.949 234.882 798.608 235.157 798.193 235.353C797.782 235.544 797.32 235.64 796.807 235.64C796.158 235.64 795.582 235.498 795.078 235.213C794.578 234.928 794.186 234.508 793.902 233.953C793.617 233.398 793.475 232.716 793.475 231.909C793.475 231.153 793.603 230.493 793.86 229.928C794.121 229.359 794.499 228.915 794.994 228.598C795.493 228.281 796.097 228.122 796.807 228.122ZM802.393 232.182C802.393 232.639 802.468 233.055 802.617 233.428C802.771 233.797 803.002 234.091 803.31 234.31C803.623 234.529 804.013 234.639 804.479 234.639C804.941 234.639 805.338 234.532 805.669 234.317C806.005 234.102 806.22 233.792 806.313 233.386H807.538C807.45 233.876 807.254 234.289 806.95 234.625C806.647 234.961 806.281 235.215 805.851 235.388C805.422 235.556 804.974 235.64 804.507 235.64C803.831 235.64 803.236 235.493 802.722 235.199C802.209 234.905 801.808 234.483 801.518 233.932C801.229 233.381 801.084 232.721 801.084 231.951C801.084 231.19 801.215 230.523 801.476 229.949C801.738 229.375 802.116 228.927 802.61 228.605C803.11 228.283 803.709 228.122 804.409 228.122C805.091 228.122 805.667 228.269 806.138 228.563C806.61 228.857 806.967 229.272 807.209 229.809C807.457 230.341 807.58 230.973 807.58 231.706V232.182H802.393ZM802.4 231.307H806.334C806.334 230.892 806.264 230.518 806.124 230.187C805.984 229.851 805.77 229.587 805.48 229.396C805.196 229.2 804.836 229.102 804.402 229.102C803.95 229.102 803.574 229.212 803.275 229.431C802.981 229.646 802.76 229.923 802.61 230.264C802.466 230.6 802.396 230.948 802.4 231.307Z" fill="#CBD5E1"/> +<g filter="url(#filter6_ddd_336_13394)"> +<rect x="689" y="248.5" width="20" height="20" rx="4" fill="white"/> +<rect x="692.066" y="251.566" width="13.8672" height="13.8672" fill="url(#pattern4)"/> +</g> +<path d="M721.774 263.64C721.298 263.64 720.836 263.582 720.388 263.465C719.94 263.344 719.534 263.164 719.17 262.926C718.806 262.688 718.503 262.394 718.26 262.044C718.022 261.689 717.873 261.279 717.812 260.812H719.17C719.254 261.195 719.42 261.517 719.667 261.778C719.919 262.039 720.225 262.235 720.584 262.366C720.948 262.497 721.342 262.562 721.767 262.562C722.243 262.562 722.668 262.497 723.041 262.366C723.419 262.231 723.715 262.039 723.93 261.792C724.149 261.545 724.259 261.248 724.259 260.903C724.259 260.6 724.189 260.336 724.049 260.112C723.914 259.888 723.715 259.701 723.454 259.552C723.193 259.403 722.875 259.286 722.502 259.202L720.514 258.74C719.767 258.577 719.182 258.285 718.757 257.865C718.337 257.445 718.122 256.89 718.113 256.199C718.113 255.616 718.267 255.102 718.575 254.659C718.888 254.216 719.315 253.87 719.856 253.623C720.402 253.376 721.025 253.252 721.725 253.252C722.532 253.252 723.211 253.39 723.762 253.665C724.313 253.94 724.728 254.302 725.008 254.75C725.288 255.198 725.43 255.676 725.435 256.185H724.098C724.056 255.728 723.921 255.366 723.692 255.1C723.468 254.829 723.186 254.638 722.845 254.526C722.504 254.409 722.133 254.351 721.732 254.351C721.443 254.351 721.163 254.388 720.892 254.463C720.626 254.533 720.386 254.64 720.171 254.785C719.961 254.93 719.793 255.109 719.667 255.324C719.541 255.539 719.478 255.788 719.478 256.073C719.478 256.437 719.602 256.733 719.849 256.962C720.101 257.191 720.556 257.387 721.214 257.55L723.139 258.012C723.643 258.119 724.054 258.278 724.371 258.488C724.693 258.698 724.94 258.936 725.113 259.202C725.29 259.468 725.412 259.746 725.477 260.035C725.547 260.324 725.582 260.609 725.582 260.889C725.582 261.398 725.43 261.862 725.127 262.282C724.824 262.697 724.387 263.029 723.818 263.276C723.253 263.519 722.572 263.64 721.774 263.64ZM727.012 258.46C727.012 257.349 727.189 256.404 727.544 255.625C727.903 254.846 728.412 254.253 729.07 253.847C729.728 253.441 730.505 253.238 731.401 253.238C732.302 253.238 733.079 253.441 733.732 253.847C734.39 254.253 734.894 254.846 735.244 255.625C735.599 256.404 735.776 257.349 735.776 258.46C735.776 259.505 735.62 260.404 735.307 261.155C734.999 261.902 734.497 262.499 733.802 262.947C733.975 263.33 734.229 263.612 734.565 263.794C734.906 263.981 735.284 264.081 735.699 264.095V265.194C735.368 265.194 735.018 265.152 734.649 265.068C734.28 264.989 733.919 264.828 733.564 264.585C733.214 264.347 732.892 263.99 732.598 263.514C732.365 263.556 732.155 263.586 731.968 263.605C731.781 263.628 731.592 263.64 731.401 263.64C730.51 263.64 729.735 263.442 729.077 263.045C728.419 262.644 727.91 262.058 727.551 261.288C727.192 260.518 727.012 259.575 727.012 258.46ZM728.447 258.488C728.447 259.879 728.704 260.898 729.217 261.547C729.735 262.191 730.463 262.513 731.401 262.513C732.344 262.513 733.072 262.189 733.585 261.54C734.103 260.891 734.362 259.867 734.362 258.467C734.362 257.081 734.105 256.052 733.592 255.38C733.083 254.703 732.353 254.365 731.401 254.365C730.454 254.365 729.723 254.703 729.21 255.38C728.701 256.057 728.447 257.093 728.447 258.488ZM738.059 253.378H739.382V262.338H744.163V263.5H738.052L738.059 253.378Z" fill="#CBD5E1"/> +<g filter="url(#filter7_ddd_336_13394)"> +<rect x="689" y="276.5" width="20" height="20" rx="4" fill="white"/> +</g> +<rect x="693" y="281" width="12" height="12" fill="url(#pattern5)"/> +<path d="M725.337 281.378L721.907 287.195V291.5H720.619V287.188L717.168 281.378H718.582L721.27 286.033L723.916 281.378H725.337ZM728.577 291.64C727.91 291.64 727.326 291.498 726.827 291.213C726.328 290.924 725.94 290.501 725.665 289.946C725.39 289.391 725.252 288.712 725.252 287.909C725.252 287.153 725.38 286.49 725.637 285.921C725.894 285.352 726.269 284.911 726.764 284.598C727.263 284.281 727.868 284.122 728.577 284.122C729.244 284.122 729.823 284.269 730.313 284.563C730.808 284.852 731.19 285.279 731.461 285.844C731.732 286.409 731.867 287.097 731.867 287.909C731.867 288.646 731.741 289.295 731.489 289.855C731.242 290.415 730.873 290.854 730.383 291.171C729.898 291.484 729.296 291.64 728.577 291.64ZM728.577 290.618C729.011 290.618 729.377 290.508 729.676 290.289C729.975 290.07 730.201 289.755 730.355 289.344C730.509 288.933 730.586 288.446 730.586 287.881C730.586 287.363 730.518 286.899 730.383 286.488C730.248 286.073 730.031 285.744 729.732 285.501C729.438 285.258 729.053 285.137 728.577 285.137C728.138 285.137 727.767 285.247 727.464 285.466C727.161 285.681 726.93 285.993 726.771 286.404C726.617 286.815 726.54 287.307 726.54 287.881C726.54 288.39 726.61 288.852 726.75 289.267C726.89 289.682 727.109 290.011 727.408 290.254C727.707 290.497 728.096 290.618 728.577 290.618ZM736.112 291.64C735.734 291.635 735.377 291.582 735.041 291.479C734.71 291.372 734.416 291.215 734.159 291.01C733.902 290.805 733.699 290.55 733.55 290.247C733.405 289.939 733.333 289.582 733.333 289.176V284.262H734.628V289.043C734.628 289.514 734.773 289.897 735.062 290.191C735.356 290.485 735.797 290.632 736.385 290.632C736.917 290.632 737.344 290.494 737.666 290.219C737.993 289.939 738.156 289.524 738.156 288.973V284.262H739.444V291.5H738.415L738.282 290.114C738.193 290.478 738.042 290.772 737.827 290.996C737.617 291.22 737.363 291.386 737.064 291.493C736.77 291.596 736.453 291.645 736.112 291.64ZM745.237 285.207H743.606V289.708C743.606 289.965 743.631 290.154 743.683 290.275C743.739 290.392 743.83 290.466 743.956 290.499C744.086 290.532 744.264 290.548 744.488 290.548H745.279V291.416C745.19 291.449 745.055 291.477 744.873 291.5C744.695 291.523 744.474 291.535 744.208 291.535C743.708 291.535 743.321 291.467 743.046 291.332C742.77 291.197 742.577 290.996 742.465 290.73C742.357 290.464 742.304 290.137 742.304 289.75V285.207H741.128V284.262H742.339L742.654 282.155H743.606V284.255H745.237V285.207ZM749.688 291.64C749.31 291.635 748.953 291.582 748.617 291.479C748.286 291.372 747.992 291.215 747.735 291.01C747.478 290.805 747.275 290.55 747.126 290.247C746.981 289.939 746.909 289.582 746.909 289.176V284.262H748.204V289.043C748.204 289.514 748.349 289.897 748.638 290.191C748.932 290.485 749.373 290.632 749.961 290.632C750.493 290.632 750.92 290.494 751.242 290.219C751.569 289.939 751.732 289.524 751.732 288.973V284.262H753.02V291.5H751.991L751.858 290.114C751.769 290.478 751.618 290.772 751.403 290.996C751.193 291.22 750.939 291.386 750.64 291.493C750.346 291.596 750.029 291.645 749.688 291.64ZM758.699 291.64C758.274 291.64 757.915 291.584 757.621 291.472C757.331 291.36 757.093 291.218 756.907 291.045C756.72 290.868 756.575 290.688 756.473 290.506C756.37 290.319 756.295 290.156 756.249 290.016L756.109 291.5H755.122V281.098H756.424V285.41C756.484 285.289 756.575 285.156 756.697 285.011C756.823 284.866 756.984 284.726 757.18 284.591C757.376 284.456 757.607 284.344 757.873 284.255C758.143 284.166 758.451 284.122 758.797 284.122C759.702 284.122 760.421 284.437 760.953 285.067C761.489 285.697 761.758 286.626 761.758 287.853C761.758 288.614 761.643 289.279 761.415 289.848C761.186 290.413 760.845 290.854 760.393 291.171C759.94 291.484 759.375 291.64 758.699 291.64ZM758.468 290.646C759.056 290.646 759.534 290.42 759.903 289.967C760.271 289.51 760.456 288.784 760.456 287.79C760.456 286.945 760.283 286.292 759.938 285.83C759.597 285.363 759.098 285.13 758.44 285.13C757.959 285.13 757.572 285.235 757.278 285.445C756.988 285.65 756.774 285.951 756.634 286.348C756.498 286.745 756.428 287.225 756.424 287.79C756.424 288.803 756.58 289.533 756.893 289.981C757.21 290.424 757.735 290.646 758.468 290.646ZM764.372 288.182C764.372 288.639 764.447 289.055 764.596 289.428C764.75 289.797 764.981 290.091 765.289 290.31C765.602 290.529 765.991 290.639 766.458 290.639C766.92 290.639 767.317 290.532 767.648 290.317C767.984 290.102 768.199 289.792 768.292 289.386H769.517C769.428 289.876 769.232 290.289 768.929 290.625C768.626 290.961 768.259 291.215 767.83 291.388C767.401 291.556 766.953 291.64 766.486 291.64C765.809 291.64 765.214 291.493 764.701 291.199C764.188 290.905 763.786 290.483 763.497 289.932C763.208 289.381 763.063 288.721 763.063 287.951C763.063 287.19 763.194 286.523 763.455 285.949C763.716 285.375 764.094 284.927 764.589 284.605C765.088 284.283 765.688 284.122 766.388 284.122C767.069 284.122 767.646 284.269 768.117 284.563C768.588 284.857 768.945 285.272 769.188 285.809C769.435 286.341 769.559 286.973 769.559 287.706V288.182H764.372ZM764.379 287.307H768.313C768.313 286.892 768.243 286.518 768.103 286.187C767.963 285.851 767.748 285.587 767.459 285.396C767.174 285.2 766.815 285.102 766.381 285.102C765.928 285.102 765.553 285.212 765.254 285.431C764.96 285.646 764.738 285.923 764.589 286.264C764.444 286.6 764.374 286.948 764.379 287.307Z" fill="#CBD5E1"/> +<g filter="url(#filter8_ddd_336_13394)"> +<rect x="689" y="304.5" width="20" height="20" rx="4" fill="white"/> +<rect x="693" y="308.5" width="12" height="12" fill="url(#pattern6)"/> +</g> +<path d="M717.889 319.5V318.786L723.433 310.505H718.127V309.378H725.246V310.064L719.653 318.359H725.218V319.5H717.889ZM729.055 319.64C728.589 319.64 728.176 319.558 727.816 319.395C727.457 319.227 727.175 318.987 726.969 318.674C726.769 318.361 726.668 317.983 726.668 317.54C726.668 316.737 726.948 316.14 727.508 315.748C728.068 315.356 728.967 315.146 730.203 315.118L731.498 315.083V314.516C731.498 314.073 731.365 313.723 731.099 313.466C730.833 313.205 730.42 313.074 729.86 313.074C729.445 313.079 729.079 313.177 728.761 313.368C728.449 313.559 728.246 313.872 728.152 314.306H727.011C727.039 313.835 727.172 313.438 727.41 313.116C727.648 312.789 727.98 312.542 728.404 312.374C728.834 312.206 729.34 312.122 729.923 312.122C730.553 312.122 731.076 312.211 731.491 312.388C731.911 312.565 732.224 312.827 732.429 313.172C732.639 313.517 732.744 313.942 732.744 314.446V319.5H731.638L731.547 318.142C731.267 318.707 730.91 319.099 730.476 319.318C730.047 319.533 729.573 319.64 729.055 319.64ZM729.447 318.709C729.69 318.709 729.93 318.665 730.168 318.576C730.411 318.487 730.633 318.368 730.833 318.219C731.034 318.065 731.193 317.895 731.309 317.708C731.431 317.517 731.494 317.321 731.498 317.12V315.881L730.42 315.902C729.87 315.911 729.408 315.967 729.034 316.07C728.666 316.173 728.388 316.334 728.201 316.553C728.019 316.772 727.928 317.059 727.928 317.414C727.928 317.82 728.068 318.137 728.348 318.366C728.633 318.595 728.999 318.709 729.447 318.709ZM734.956 321.754V312.262H736.244L736.258 313.522C736.342 313.373 736.449 313.216 736.58 313.053C736.715 312.89 736.878 312.738 737.07 312.598C737.266 312.458 737.492 312.344 737.749 312.255C738.01 312.166 738.306 312.122 738.638 312.122C739.216 312.122 739.725 312.255 740.164 312.521C740.607 312.782 740.952 313.184 741.2 313.725C741.447 314.266 741.571 314.952 741.571 315.783C741.571 316.614 741.449 317.316 741.207 317.89C740.969 318.459 740.623 318.893 740.171 319.192C739.718 319.491 739.172 319.64 738.533 319.64C738.211 319.64 737.924 319.593 737.672 319.5C737.424 319.411 737.207 319.295 737.021 319.15C736.839 319.001 736.685 318.842 736.559 318.674C736.437 318.506 736.337 318.345 736.258 318.191V321.754H734.956ZM738.288 318.632C738.885 318.632 739.373 318.406 739.751 317.953C740.129 317.496 740.318 316.786 740.318 315.825C740.318 314.999 740.145 314.343 739.8 313.858C739.454 313.373 738.95 313.13 738.288 313.13C737.616 313.13 737.107 313.38 736.762 313.879C736.416 314.374 736.244 315.022 736.244 315.825C736.244 316.343 736.321 316.814 736.475 317.239C736.629 317.664 736.857 318.002 737.161 318.254C737.464 318.506 737.84 318.632 738.288 318.632ZM744.668 312.262V319.5H743.415V312.262H744.668ZM744.696 309.385V310.729H743.373V309.385H744.696ZM747.87 316.182C747.87 316.639 747.945 317.055 748.094 317.428C748.248 317.797 748.479 318.091 748.787 318.31C749.1 318.529 749.489 318.639 749.956 318.639C750.418 318.639 750.815 318.532 751.146 318.317C751.482 318.102 751.697 317.792 751.79 317.386H753.015C752.926 317.876 752.73 318.289 752.427 318.625C752.124 318.961 751.757 319.215 751.328 319.388C750.899 319.556 750.451 319.64 749.984 319.64C749.307 319.64 748.712 319.493 748.199 319.199C747.686 318.905 747.284 318.483 746.995 317.932C746.706 317.381 746.561 316.721 746.561 315.951C746.561 315.19 746.692 314.523 746.953 313.949C747.214 313.375 747.592 312.927 748.087 312.605C748.586 312.283 749.186 312.122 749.886 312.122C750.567 312.122 751.144 312.269 751.615 312.563C752.086 312.857 752.443 313.272 752.686 313.809C752.933 314.341 753.057 314.973 753.057 315.706V316.182H747.87ZM747.877 315.307H751.811C751.811 314.892 751.741 314.518 751.601 314.187C751.461 313.851 751.246 313.587 750.957 313.396C750.672 313.2 750.313 313.102 749.879 313.102C749.426 313.102 749.051 313.212 748.752 313.431C748.458 313.646 748.236 313.923 748.087 314.264C747.942 314.6 747.872 314.948 747.877 315.307ZM754.835 319.5V312.262H756.067V313.655C756.188 313.31 756.358 313.025 756.578 312.801C756.797 312.572 757.047 312.402 757.327 312.29C757.611 312.178 757.905 312.122 758.209 312.122C758.316 312.122 758.421 312.129 758.524 312.143C758.626 312.157 758.706 312.18 758.762 312.213V313.466C758.692 313.433 758.601 313.412 758.489 313.403C758.381 313.389 758.29 313.382 758.216 313.382C757.926 313.363 757.656 313.382 757.404 313.438C757.152 313.489 756.93 313.58 756.739 313.711C756.547 313.842 756.396 314.014 756.284 314.229C756.176 314.439 756.123 314.696 756.123 314.999V319.5H754.835Z" fill="#CBD5E1"/> +<rect x="689" y="332.5" width="20" height="20" rx="4" fill="#65E7B9" fill-opacity="0.2"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M694.5 342.5C694.5 341.948 694.948 341.5 695.5 341.5C696.052 341.5 696.5 341.948 696.5 342.5C696.5 343.052 696.052 343.5 695.5 343.5C694.948 343.5 694.5 343.052 694.5 342.5ZM698 342.5C698 341.948 698.448 341.5 699 341.5C699.552 341.5 700 341.948 700 342.5C700 343.052 699.552 343.5 699 343.5C698.448 343.5 698 343.052 698 342.5ZM701.5 342.5C701.5 341.948 701.948 341.5 702.5 341.5C703.052 341.5 703.5 341.948 703.5 342.5C703.5 343.052 703.052 343.5 702.5 343.5C701.948 343.5 701.5 343.052 701.5 342.5Z" fill="white"/> +<path d="M718.337 347.5V337.378H720.129L723.181 345.736L726.275 337.378H728.06V347.5H726.723V339.317L723.79 347.5H722.579L719.667 339.394V347.5H718.337ZM733.253 347.64C732.585 347.64 732.002 347.498 731.503 347.213C731.003 346.924 730.616 346.501 730.341 345.946C730.065 345.391 729.928 344.712 729.928 343.909C729.928 343.153 730.056 342.49 730.313 341.921C730.569 341.352 730.945 340.911 731.44 340.598C731.939 340.281 732.543 340.122 733.253 340.122C733.92 340.122 734.499 340.269 734.989 340.563C735.483 340.852 735.866 341.279 736.137 341.844C736.407 342.409 736.543 343.097 736.543 343.909C736.543 344.646 736.417 345.295 736.165 345.855C735.917 346.415 735.549 346.854 735.059 347.171C734.573 347.484 733.971 347.64 733.253 347.64ZM733.253 346.618C733.687 346.618 734.053 346.508 734.352 346.289C734.65 346.07 734.877 345.755 735.031 345.344C735.185 344.933 735.262 344.446 735.262 343.881C735.262 343.363 735.194 342.899 735.059 342.488C734.923 342.073 734.706 341.744 734.408 341.501C734.114 341.258 733.729 341.137 733.253 341.137C732.814 341.137 732.443 341.247 732.14 341.466C731.836 341.681 731.605 341.993 731.447 342.404C731.293 342.815 731.216 343.307 731.216 343.881C731.216 344.39 731.286 344.852 731.426 345.267C731.566 345.682 731.785 346.011 732.084 346.254C732.382 346.497 732.772 346.618 733.253 346.618ZM738.237 347.5V340.262H739.469V341.655C739.59 341.31 739.761 341.025 739.98 340.801C740.199 340.572 740.449 340.402 740.729 340.29C741.014 340.178 741.308 340.122 741.611 340.122C741.718 340.122 741.823 340.129 741.926 340.143C742.029 340.157 742.108 340.18 742.164 340.213V341.466C742.094 341.433 742.003 341.412 741.891 341.403C741.784 341.389 741.693 341.382 741.618 341.382C741.329 341.363 741.058 341.382 740.806 341.438C740.554 341.489 740.332 341.58 740.141 341.711C739.95 341.842 739.798 342.014 739.686 342.229C739.579 342.439 739.525 342.696 739.525 342.999V347.5H738.237ZM744.438 344.182C744.438 344.639 744.513 345.055 744.662 345.428C744.816 345.797 745.047 346.091 745.355 346.31C745.668 346.529 746.058 346.639 746.524 346.639C746.986 346.639 747.383 346.532 747.714 346.317C748.05 346.102 748.265 345.792 748.358 345.386H749.583C749.495 345.876 749.299 346.289 748.995 346.625C748.692 346.961 748.326 347.215 747.896 347.388C747.467 347.556 747.019 347.64 746.552 347.64C745.876 347.64 745.281 347.493 744.767 347.199C744.254 346.905 743.853 346.483 743.563 345.932C743.274 345.381 743.129 344.721 743.129 343.951C743.129 343.19 743.26 342.523 743.521 341.949C743.783 341.375 744.161 340.927 744.655 340.605C745.155 340.283 745.754 340.122 746.454 340.122C747.136 340.122 747.712 340.269 748.183 340.563C748.655 340.857 749.012 341.272 749.254 341.809C749.502 342.341 749.625 342.973 749.625 343.706V344.182H744.438ZM744.445 343.307H748.379C748.379 342.892 748.309 342.518 748.169 342.187C748.029 341.851 747.815 341.587 747.525 341.396C747.241 341.2 746.881 341.102 746.447 341.102C745.995 341.102 745.619 341.212 745.32 341.431C745.026 341.646 744.805 341.923 744.655 342.264C744.511 342.6 744.441 342.948 744.445 343.307Z" fill="#CBD5E1"/> +<path d="M900 206L895 203.113L895 208.887L900 206ZM885 206.5L895.5 206.5L895.5 205.5L885 205.5L885 206.5Z" fill="white"/> +<rect x="916" y="188" width="76" height="34" rx="17" fill="white"/> +<path d="M934.252 211.16C933.222 211.16 932.332 210.928 931.58 210.464C930.828 210 930.246 209.328 929.836 208.448C929.43 207.563 929.228 206.493 929.228 205.24C929.228 203.976 929.433 202.901 929.844 202.016C930.254 201.125 930.836 200.445 931.588 199.976C932.345 199.507 933.233 199.272 934.252 199.272C935.276 199.272 936.161 199.507 936.908 199.976C937.654 200.44 938.23 201.117 938.636 202.008C939.041 202.893 939.244 203.971 939.244 205.24C939.244 206.493 939.041 207.563 938.636 208.448C938.236 209.328 937.662 210 936.916 210.464C936.169 210.928 935.281 211.16 934.252 211.16ZM934.252 209.872C934.966 209.872 935.574 209.709 936.076 209.384C936.577 209.059 936.958 208.555 937.22 207.872C937.486 207.189 937.62 206.317 937.62 205.256C937.62 204.179 937.486 203.293 937.22 202.6C936.953 201.907 936.569 201.395 936.068 201.064C935.566 200.728 934.961 200.56 934.252 200.56C933.548 200.56 932.942 200.728 932.436 201.064C931.929 201.4 931.54 201.915 931.268 202.608C930.996 203.301 930.86 204.184 930.86 205.256C930.86 206.317 930.996 207.189 931.268 207.872C931.54 208.555 931.929 209.059 932.436 209.384C932.942 209.709 933.548 209.872 934.252 209.872ZM943.97 211.16C943.538 211.155 943.13 211.093 942.746 210.976C942.367 210.853 942.031 210.675 941.738 210.44C941.444 210.205 941.212 209.915 941.042 209.568C940.876 209.216 940.794 208.808 940.794 208.344V202.728H942.274V208.192C942.274 208.731 942.439 209.168 942.77 209.504C943.106 209.84 943.61 210.008 944.282 210.008C944.89 210.008 945.378 209.851 945.746 209.536C946.119 209.216 946.306 208.741 946.306 208.112V202.728H947.778V211H946.602L946.45 209.416C946.348 209.832 946.175 210.168 945.93 210.424C945.69 210.68 945.399 210.869 945.058 210.992C944.722 211.109 944.359 211.165 943.97 211.16ZM953.99 203.808H952.126V208.952C952.126 209.245 952.155 209.461 952.214 209.6C952.278 209.733 952.382 209.819 952.526 209.856C952.675 209.893 952.878 209.912 953.134 209.912H954.038V210.904C953.937 210.941 953.782 210.973 953.574 211C953.371 211.027 953.118 211.04 952.814 211.04C952.243 211.04 951.801 210.963 951.486 210.808C951.171 210.653 950.95 210.424 950.822 210.12C950.699 209.816 950.638 209.443 950.638 209V203.808H949.294V202.728H950.678L951.038 200.32H952.126V202.72H953.99V203.808ZM955.708 213.576V202.728H957.18L957.196 204.168C957.292 203.997 957.414 203.819 957.564 203.632C957.718 203.445 957.905 203.272 958.124 203.112C958.348 202.952 958.606 202.821 958.9 202.72C959.198 202.619 959.537 202.568 959.916 202.568C960.577 202.568 961.158 202.72 961.66 203.024C962.166 203.323 962.561 203.781 962.844 204.4C963.126 205.019 963.268 205.803 963.268 206.752C963.268 207.701 963.129 208.504 962.852 209.16C962.58 209.811 962.185 210.307 961.668 210.648C961.15 210.989 960.526 211.16 959.796 211.16C959.428 211.16 959.1 211.107 958.812 211C958.529 210.899 958.281 210.765 958.068 210.6C957.86 210.429 957.684 210.248 957.54 210.056C957.401 209.864 957.286 209.68 957.196 209.504V213.576H955.708ZM959.516 210.008C960.198 210.008 960.756 209.749 961.188 209.232C961.62 208.709 961.836 207.899 961.836 206.8C961.836 205.856 961.638 205.107 961.244 204.552C960.849 203.997 960.273 203.72 959.516 203.72C958.748 203.72 958.166 204.005 957.772 204.576C957.377 205.141 957.18 205.883 957.18 206.8C957.18 207.392 957.268 207.931 957.444 208.416C957.62 208.901 957.881 209.288 958.228 209.576C958.574 209.864 959.004 210.008 959.516 210.008ZM967.715 211.16C967.283 211.155 966.875 211.093 966.491 210.976C966.112 210.853 965.776 210.675 965.483 210.44C965.189 210.205 964.957 209.915 964.787 209.568C964.621 209.216 964.539 208.808 964.539 208.344V202.728H966.019V208.192C966.019 208.731 966.184 209.168 966.515 209.504C966.851 209.84 967.355 210.008 968.027 210.008C968.635 210.008 969.123 209.851 969.491 209.536C969.864 209.216 970.051 208.741 970.051 208.112V202.728H971.523V211H970.347L970.195 209.416C970.093 209.832 969.92 210.168 969.675 210.424C969.435 210.68 969.144 210.869 968.803 210.992C968.467 211.109 968.104 211.165 967.715 211.16ZM977.735 203.808H975.871V208.952C975.871 209.245 975.9 209.461 975.959 209.6C976.023 209.733 976.127 209.819 976.271 209.856C976.42 209.893 976.623 209.912 976.879 209.912H977.783V210.904C977.681 210.941 977.527 210.973 977.319 211C977.116 211.027 976.863 211.04 976.559 211.04C975.988 211.04 975.545 210.963 975.231 210.808C974.916 210.653 974.695 210.424 974.567 210.12C974.444 209.816 974.383 209.443 974.383 209V203.808H973.039V202.728H974.423L974.783 200.32H975.871V202.72H977.735V203.808Z" fill="#0F172A"/> +</g> +<defs> +<filter id="filter0_ddd_336_13394" x="688" y="79.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<filter id="filter1_ddd_336_13394" x="688" y="107.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_336_13394" transform="scale(0.0047619 0.00333333)"/> +</pattern> +<filter id="filter2_ddd_336_13394" x="688" y="135.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<filter id="filter3_ddd_336_13394" x="688" y="163.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern1" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image1_336_13394" transform="matrix(0.00313079 0 0 0.0031746 -0.445135 0)"/> +</pattern> +<filter id="filter4_ddd_336_13394" x="688" y="191.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern2" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image2_336_13394" transform="scale(0.00195312)"/> +</pattern> +<filter id="filter5_ddd_336_13394" x="688" y="219.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern3" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image3_336_13394" transform="matrix(0.00557364 0 0 0.00583805 -0.341483 -0.385836)"/> +</pattern> +<filter id="filter6_ddd_336_13394" x="688" y="247.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern4" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image4_336_13394" transform="scale(0.00195312)"/> +</pattern> +<filter id="filter7_ddd_336_13394" x="688" y="275.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern5" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image5_336_13394" transform="scale(0.00195312)"/> +</pattern> +<filter id="filter8_ddd_336_13394" x="688" y="303.5" width="22" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.09 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.05 0"/> +<feBlend mode="normal" in2="effect1_dropShadow_336_13394" result="effect2_dropShadow_336_13394"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0 0.470588 0 0 0 0.01 0"/> +<feBlend mode="normal" in2="effect2_dropShadow_336_13394" result="effect3_dropShadow_336_13394"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_336_13394" result="shape"/> +</filter> +<pattern id="pattern6" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image6_336_13394" transform="scale(0.00195312)"/> +</pattern> +<clipPath id="clip0_336_13394"> +<rect width="1016" height="433" fill="white"/> +</clipPath> +<clipPath id="clip1_336_13394"> +<rect width="24" height="24" fill="white" transform="translate(146 193)"/> +</clipPath> +<image id="image0_336_13394" width="210" height="300" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANIAAAEsCAYAAAC2du2fAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAA0qADAAQAAAABAAABLAAAAACoLskLAAAvmElEQVR4Ae19DbAdR3Vm99z7fqSnJ1lSiCUh45815azLG8ohgaQQW2wlWkIIOIHFiVkgJDjIsCU52K6iUqR2XbuwKW9heZEcR/IPZGO2vDhysJXYWBDHAjsQ/IMxODYEW5ZB1pP8I/npPf083TvTe3runfdm5k7P9PSc7um5r6fqvXunp/uc73x9zvT0udMz9Nw9s88TQnYRFuyee2Hy8YOb6AnYd5tjwDFQggEKgcTC+oy8Qil5lDHygEfZHvr3y37y7HY6V0KWq+oYWLQMLARSnIJ+UHWn2N3t1cGD+04u30cupX68ivvuGHAMLDCQHUgLx/m3/fD3XUbYV+d+2P3nqavP+CmhtDeK8aNucww4BohMIEU08cu8Kfh7AILqG2y/960XNk3wfbc5BhY9A2UCKU4WD6ofw9/XCSXf6DzlP3bgqhVH4hXcd8fAYmJANZAWOPLJNGmRH0DBd4MjwVc7xya/7zJ/C/S4b4uDgeqBFOcJkhQwQj1DGLnHP0n3jv7j0qdc5i9OkPs+rAzgBlKcJZf5i7Phvg85A/oCKUncfth1mb8kJ25viBgwFUhxyvbDjsv8xRlx3xvPQB2BFJHmMn8RE+6z8QzUGUhx8haCCu75ow92f7DvulXT8Qruu2PAZgZsCSTOEQ+mMRKl0/uZv+5LS59x6XSbXchh4wzYFEiDPZK6kfa56WXPuHv+BmlyJfUzYHcgJfnZD7th5s87HXxv328vf9bd85ckyO3Vx0CTAinO0n7YcZm/OCPue60MNDWQItIWkhTunr+IE/dZAwNND6Q4ZQtB5TJ/cV7cdwMM0HPv76+QLauMlm1gsL7L/Bkk26niDKgHUhP448HuMn9N6KnGY/RyLeDrYLH/chUiH+yt4/05eA7Fu0Dy5wNC/+95S46dh6zFiXMMkHYYKCaJyFqkbuoy0Sdn01FvzKS5TtfiYKBthZlZwaUDmEc6OsQ6mY4Bc4GEHSwqo1hARjpzbkRybo/PgPlLOywbeGD2kglJiSoBlpTg9hwDpRlQCyRbnDVrlMsqK02La+AYKMeA2qWdyFl1BJhIVzk7e7V14FPB4doMHQNqgSSiAdPpRTpcuWPAQgba8BtLrRs8b9xtjoHGM9C22ZGxg9xmWxvvSYvcAC0jEpbDYslZ5H3szDfAAO4cqQ8YeyTB4sEFJhaTTk6aAeVAwg4W7U4egOn5dxamuXH7jgFpBrRc2klrj1Xkgak1mFwQxdh2X7EZUB6RsIFwedij3ADGjvFbdAcguILhZKDNXxnGDKagsV9RZhL7cLqAswqDgXBEwnZuDGCyMkTYXYDJMujqYTCgdq8d12xwFFMxVBRgKrJcG8dAEQNtowFR810URWS4444BVQaSI5LuUUa3/CIWXCAXMeSOKzKQzNo13dHqDlTFTnDNms9AckRSsccm5236iUCFf9fGCgaSI5IKJJHz6ggwkS5Z3Dowyep29YaageqBJKKnqtOL5Lpyx4CFDFQLJB3B4kYNC93EQSpioPocqUiDO+4YWAQMtElATsNvSaNGf0/KI1bHKBfpc6NdxIT7RGag2qWdKhjsYHEBotoTrh0SAwuBhO3cSAClxIiwuwCTos9Vqs5AL5BEjqgq3xYHTtvVhQtYty5JtVdduxwGeoGEfodnTiSlnTsHnDvkGGgKA71A4suw41vls3ZOtOTEWByClu9ubYUWWp1QAq914Vt6RMqJA62kYTh65ZOAVgud8CFloD9HqnOYkGQ2HeyiZrkngQbYKbLLlVvNQC+QrIbYB4cxWrk4akJPNxKjuxBqZLc50LYxgDMi5V5OKZisY+TgGHXIVTDPNRk+BtqsyxhdDOOSC6Lh816LLApHpOh5clof0FjGaOwRDnSHD6BslQHh6joG5BkIA0kmgKJgkxdtvmaeHXnHzCN1GoeNAek5UtMdkfkwRVoMl7DD5qENsSdxaWcjZqwApnBZxzrMoywYs9FOh6nZDAgDCcuBq9KDdUlpiz1V+XDt7WRA+tJOJ3ysYIkwZgUNto5Il/t0DHAGhIHkHM85iGNAnoEwkGRvYysSi3EXT5EOmeOik4D7KUmGPVdHhQHhiKQiTBSQOgJMpEsFt2vjGKjKAGogicA4pxcx48qHhQG8QMK+G8Fdhw2Ljy0KO3qBhB0EGNS5m0wxWHQyDDHQhqfaEZK+B82W0QA7wG2xy1DnOjXmGGjDoyEJPCRSfsN2bnnNcjVdsMjx5GqhMlB+juQcFbUDnLDhYCD70s4221zw2tYjDk+Kgfxkgy0OjHU5aYs9qU5wu81noPylnS6bsYIlwueCJmLCfRpgID+QsJ3bgEHzKtLY+T5fj9Sdr+G+OAbQGMi/tFNVY+NoYCMmVX5dO+sYyB+RVOGmR4NIjg5nTuviOtJlff1szq2SjbrCfeIyoCeQRBgFDi6qrlRuQocSMNdomBnADSQdTow1ivH5ETy3wW2OAR0M6Jkj6UBaVWaZuzeq6nLtFx0D4kDCGgmqUqpjlKuKybV3DKQY6AVS2aBpgnOXtSlFjNt1DJRhQG2O5Jy0DMeu7iJgQHxpZ5vxLnht6xGHJ8ZAO3wCaZ6T5h2LCdL+tQmXk9pJcApsZaD40o47sKlg0hEsprDb2sMOlxEGigOJw9Dh4EbMazh2Uxw5PZUZmA8k0bPgVDVkPe1UVZZr5xiwnYEwkLCDiBstkmkqwDL1u8s82/2xsfjmRyRTFmQ6uCnlTo9jQBMDvUCyfQ7kRhJN3e/EYjHQC6QmO2qZk0CT7cTqcSdHCwNtSG2fDgIifPmWtpfcYTk1lhwt9Dqhi4WBcERSDZZKN1SXGUkUe2PALhd0iky6ZkUMVEo2DDhqWpuOYHHBkGbZ7VvAQC8WuMPr+LPAQHQId7LW2TuPr4X8vgtpdHKbK7BNOvCub5n3rtjiNlVGObDV74QPaVbusXVHyZh/iv2P9duOE7Jj5r4W9b7zwqaJKWWBruFQMFDu0i7txLqCK62nKtWIOA9uoife8Fczj8Dz0nfC470+xOaC/et3zuyhc/Re1vUfO3DViiNV4br2zWNAPpCynDurzEYOMHHCJV3n+tf2jEy2f0xGyAX8D66PLyBjbBM77j39hp2z3wgC9rXR/Z0n9l23atpGOhwmfAaSgYTpcBHW9GjAdaTLorpN+KSUTd3JDqw/OrvHYxBA0eaTMW8puZgE7GKP0f/SOXvkibO2z9zP2sE3Am/5U3wki6q6z+FjIJls0GEfD5z4nw4deTJ16L6U+nBp91XCKEyUYlv0ewBlE3QJ2UCXkc96be/+Fpu9G0aqP1m/ffbfnb+ZCX+zi0lyXxvGQHJEMgWeO3fDt8Bb9gjtzn6bjpCNmaZEQeWRlbTF67CNcNY6OHfh7KMwx7qvvTp4cN/J5fsID0q3NZ6B3ojEzYifuW39bhHd/FKNjpNd8CzxbiF3PKj43whZB4F3CbTY2T3o7T3r5dnb1980+6G1nz96tkunW9S5ClAWRqQmz1tkRzhkGzuvdHnS4TkIpIW5kqgTuO5olOJ1WhBULXIZ9dkHPJAB6fSHXTpdRJ795W3SZcHAO2RN4sZwbgwZCjZPXX3GT9ffDEkHKhFIIvkjhJ/MLvBa7AL45tLpIp4sL1+4tKsLqMnLSGwbIYNH59guGJGOKouOLvu4gC7cPNziQUW20KXkbyEP+A+QpLhu/V/OvOO8Tx9ZoazDNdTOwMKlnS5VspddZfTXNAJlQfTHJh9v+bOP9RIKWTVUy9iENw7pdMbT6eQTkE5/0qXTVbnU3y6Z/tYxOui3oVYN80mHQCLpUPakwkcr3qZFJiFJsQGSGy6dXmtvi5X3RiTZDrZlJJDFm7ZbE3465/0dI8FVoK446cCxV8PRS6dTSKePunR6uovr2i83R+JOkPWnA32WnqplOnCCzBc+vvQQRMc90uKr2sHb89HKg8xfG9Lpp106XZp7TRXLBZIIBIZjpGWIdFUoZx2GY28aA086LA3ugZFmIemQtqfqflon349k8u8UgmqUXAbZvy9BSn4PpNNvWb9j5n3hkg9+3G1aGZBPNvBOw9yqXd5gIkGR1Tkx+f1WMPstWJRyCYrAtBAR/3EeeR0KSXRIx3tLIZ0ekD+AS87n3N3paTLx9+WTDdi6RY6BrYfL47o06zv4cXISbgzfBZrMvjc9si3+GU+p89+oCNlCRtjdUTr9rG3H3u7S6dwx8Db5EQlP54Ikzc69oMjAN7i8a+08/gAj7DnQVpx0KANJnaf5/oUHcy6BzF+YTqej1KXTy/AvUTc5Z4if1Wz9LmFUXVXmkw7Y3GEZFKXTKaTTW5BOH3XpdCxqk4HEr7dt/8uzvMiB89piHINRKQj8uwKfvDp/ORnHhKEDQ0Yckw93pxO4O52xG7xRdj/cnf4VuDv94+f93fQbCTyfAkPdYpDR/x2JM1vjhvVA8PjEO8scA2byRXwtMvswnJDKJR0MYMuiJFHGIPPHcZ8il8Dd6QfP6s5+k900e59/ovMQv6+QwIkiUd/tzDNA37Bj5hicjSbnS4b4CyP0JHjCbx34xORenWbypRE8DQ2j0vwcRac+rbJpmDx5LjhFH4bl9O5hLwKyczra9pNP0fAjsNhAcatFe0kHBilo27eiq4HeyeACb4xdAC9A+CCjwU9dOn2wU/uXdoMHepOlrHKLyoSxJDoJCBugGsUfz/X6v5je1fLIZ8SCzWAR6+8fKfF6EEC8BH6bCh/2wkbIH5OW9yOw8z7Pp3tGXuz+YDE/7CUnkHK6wBIfEP82JAAoKM6xVPkQ/DC7Owi8KzzKVmcKycUiOhFkSqqhkMIUkC0BGy+GE+7FpEW3LPa703Mu7XL6J6ufcx0jR1bRoSxdRW1Ex3VhzNA3/vTyJyED9jAEe3bSIdcug0AzsJcugjk2IN4AM8INLPA+1aKz34N1VPcFp8kD4/868a/PbqdzpWU2rEGY/kZ5+gZ3DB1/DSM0gsudh9/pEHThllJMXiIF0WduQEaVDH0CFj4C04BtJH6QSKevv3b6/GFOp7eD04zCEJ14nIAh2kM1GE/QtvXHDp508Bn9FzjDwCUQ0lZ34JTR32W9dPoJcom3FtLpLw9vOj1Mf/u+nelv9F8tAjJLRuh7dKe/4yEDk/HPtgj9jGjUt/UkELcB9Xub8nsRhy6dPj8iqZCFMZrk6cWW743QVpkTah422WPBUXY3XeldQVl20oHftVPXhsFv2RMBXAG1PQ8yf22eTqcf9P3gR5BOf6jpz05XSzb0ex59xNDsUbAeyYcRSbOWpPiJwyt+OLd69mF4K2J20iFZ3Yq9Mv1a+kQA9AfR2cxnS8Dgi1mXXAw31DY6nU7Xbzs2Aystlwl7MDJaWEHhgFlfXgBYw6UdV84X2JHT7A7geXQBDP43jBEGH5W8RB7A3AaYW87AQ+KeJD5pzLPT1X5Hkucmu2ZWcOoKLq5Ll+xs6wZK+TuU/FbwL/yJQAMHEQvKjCSIatFERSeC/px9A5x4NtCGpNMrXdqhMcgFZQUXlgKdsiUw9u90uA9+xqweSDpsqflEE1GYfSJgq+HSbyOs89oIF+bzz04P9gf/eODC5c/b8ux0s4FUpxNwZ9GhP/KCgk8atL5CPHYFTBCy73QoaD80hyv0ATzoZR3z2SVsllzCzqBWpdN765G4cSb+6vSGCh2IAZv/wg+x/PUEzxiCMWSY6PtIRxW8kQz4DIMqYJeRbnCbDQ97UZ8jWXI5ID3K1IyX3+kASYddMIF+P/hSL+nAHSNr04FVpCtLv0yZDowyeqM6kT2UjEJK/QIY7S+Ae0g+zOeidaTT1S/tIkMiw6JPHQSLdEU6ZT65jIAnv2GBdU1b9wXyUHs93OngFyQdMOzVbaMIo47+57aI9CXtHOVvTISAuhjmVH8ED3v5iam709UDKWnAwp6cwQv1TX6DWwxMqkvrOvTnky/D87vvgpUL1ZMOaeG1WhYDU3f/L+hfBm9auZhSCkFFroS707/Pn53enfb30NeteBr7VaTql3Yx7ip/tcUJKhtSLABW6e5mjH0S1tOtK65dogZ3IFM8LjhrCYAFVXVgj2Qysgx+jN8ACDa0VrY+RRn+3em9QIoUFthay2HMTgvgAqHsPS3IRh84c+Lp8ObNLkyUsTdMrrCx1Skv7t9dSKd3IJ3uQTo9wEun42TtdJLEScD6q/nSLqQJ3hkLz+bZBfO1uUQGjwdBHX86+66MbFO2c0zcn0Afvypgc+yS4DW2E9Lp36zyKlKcORInoQmbBSNSSNOJYC9MhJ/m1/BW02Z7v8ZHGlUi+zLgYx3jVwkBex+k0/fzV5EG//PY7pHVrUf5D+pF4tuQ4ZiDlY3ie+0iCdikYpAQYWvY54GrVhzRlnTgXGBxiyWnjv5R9VdKxoITvXS6twzS6V25dHp9yQaRoU3uvBIOEyYdCCQd4ExYoplcVRG3cq3rrYXV/1XkRG0Z/N7Hrxp4Or2Vn05fCKSocb00yv5eUDfKyvrnkw4dxaQDdrC4/hf3KecmgKs2+P2P3y8Jc9yBdHov2SAWoecIdwLMPz0o9UqFpIM/zf4f8KCWdMBGhx2Yefgw+z6SlacP6xgPKP7nQzr9FNsA9/19tjXu3d9is3cvBFIEyMQnlmGRHI65gZs3zh5mp8n3rIFuou8b2lcDfcQDitvSJqshqDZWS38PSK+xQMYJaoSXpZonHbyl9N7M0TmrAUaZDE9l6mBgwpBRBnPVunG8PVlzvUCKhqyyn3GB6e9VwepoDxj9U2bfA5amJb0fJh08cjBdnhlcGJwMKKpYIMJUUWxmc5Eu1fJMJWqFgyOSmpzBVmWD0kR9QNkax/npbNBgtRKedIDnoX0NLXDUYOC3UnXuvHbYKEW6FPQszJGixiLhTSmP7GjKJyQdguPBblhWPYdyB0ee3bb3YR52k8fK8gTYFtLfVYHyEcWGjZMg2gCjbZd2HGov6UC/B0/S+TURdJRyW/pIxZi8flWRh9kGeB0ckaooSEdyFVl5bdN6JPdh7b+VG086kA7cfydph7X1dLLLTwK2/gE0vEDiTpDedDlGWs8Q7PtH6f3wQ9/B0kFik+26+rsuuSW4LRdIOgxKg+U6FuF26K3LfhwmHcraLuqTsnJk64v0qZan9drU/7I2gQ29QCrRIG135f207soCGyrgAyQI5rp3wupZnFegpHnF2semN42Ly+dlpra0foV9PmXw4B88lhqw8z8AX/UPxf6qIETtUcBpEgIvOh5/Nfgu3NMlf6eDyM4q5ZrMKy22ig1l2pYGlt2g3KVdtoxEqbQNUHE+gDG+A4oi3QmgFu6Er44sk3Sw0IZSkIo6rOzxUspxK4eBNI8XvqA6d548sGNeL4ZNcWHx7xiyDcqgXbYbnii6P26C7HcOM123NPS0AJ37pcEVNBBhLWimdDiuCwSEgcQfFVvpDwRVzUzmGRPHXPo7NIhODnk6bDnGH8NLl3gPzEdECWCcm/SWyVeMk4ibSp+gNFMPQnnaHqV9HeBSQHqXdjqjIKUw3C1pWKUgB11Re2Lp70gJividDpB0gFv1Z0Oa4F8lJ89qDwpD2TmfCUxFOyJhRe0kjotEJ8phpw6OIgzcDPGdDTy4dG06Zedh5tZy6y3feNJhbpX3QzgBqN3poJlfaQqlK+J2iGbzM8GKkw1RuDXxM9PU5hTOJx24R6j85ZmK0J/RCK/lU9HkOE0o5gNPsqMc19cLJARyrTrTi+xpwqVd3wuCcf8uSIXvn78Gy/OOMsfiHmfj9zxbRP1aolxH8PMpg3hEyjNIdExkkKh+lXKRrrzyKvoMt526+oyfBt3gA3Tcu42MKdw6lMeD6WNY3NkY+BwTTBl6cyQsQ0VyeMe5rRwD8AMtPEztMXIn27T+8PELvTPIh/yjwaUg5JxMQbxDbd2a3v8S3OIFEjZZEuBt9RtUXJDFO0DID0Hmp9dfO30LXe39XjDDfh+uJS5K6BHxb4pHkf4EyJI7prAXwSqyDXDizZGKwJQ9XgS+rLwhqH/g2hXP/mzz5OeCUf+34br8akhEfAc+82d+nEcTf03ntwpHXdIZDCSbCKliXFZbm2yrgGXqmpUvvPhny7f6r9FL4F7JTWFAqT7Wy2aesrDpKqvQH7zpYLKBAzW16SDFFHYL9PD3LR28dvkXx4503+VP0w8Hp8h9AGvWAmjyELB9QF4zas3sOZLJYEI1B4SJsNtyvY1tL8gLf3ci5G/W7WT3sunpDeRE66N0nL0TuFilpE7EoZIww41E2HX0f0xXGx5QyCjWO4PSYLmidJlhXufV2YRlHhTul/5b6L5+/mb2zbk3zryFMe8PgteC34JnQawd6IeYE6ChsKWvswzSYW9MTxveqErhhz89WxGxmo0bMKoIz0CDZhbwFz8D8ocgdf7ttTPT8BD41kdghHoP9PM5Wi3K6k+TnGfpr2qwJP78EUlSiDJW3fKVgQ1JQ0idh79Fwe9Ra66e2eFNBL9Px+nvgnXJ1LlOc3U4t068irKz50iRsCaTkBWkPrz4knnjkXmL6fPQ9ZNPg73/de3nj97mnWq9H9JMfwgj1C/A9Ui+D9RFUlb/1YFFJgYAa49EmcqyRjSJAFmbhqgeT52DOVvP3nn8js6U/y66hFwOAfVLkJgYa4yZmP6KYfR8IGEIi2SIjNQRYCJdERb3KWSg/zrHL67fOn139yXv1+kY+yiM1f8egqr47Y1CqQUHsHwAS04B3DKHzQ3rzunL9IuxuuHDKRnbte5mci97deY3SIfCPApS575i6jwPeZN9oCB4cQNJB1EFBuT1mzsmyQDcIAuvwzgBtXdD6nzPyZXHN9DV5LIwdd7KSJ3nicX2AVv6P88uwIg/R8oj2R2znoF+6vwBSJ3vDVPn3X7qvEvOsR58VYB5wZIjm3XyAqkJZ4Ic4zIP2WJTJjjLCqPUOWOPr7lmNkydE49eRtvkfKNIFZ3bKEZQ1huRyjqY7caVtcc0603SB5d9hwgJU+ewjOOv4cVo751PnUf+U9UeHf5k2AcGb1qVIYWDtPlPxgZXpzQDfBkHv+u8vbr1H8O7zkdgGQeBZRw8EKr8lUYi0UCER6KpSpXBZRQiAHWXq1jn2mhhgKfOPcK+xU7S/aECGd/QgkRBqAzWsnUARnGywfAQKaSGG1d1s8WWqnbU2H7Nn868rjXBPhx0ySdoi50fjkRVeMXoV918SNjXCyTdQGTl6yBVggRZeIu5HqTFx06snXknOcmuAR5+DVYM4PhO0/un77PFZOhw7sXskQ20fe1nX/vlE93Zq2A29B5YkrEM7n5ozqY7ULl8iJEwkBh/Eh72pskAOrCwpgB4zDSYIDMyWlDfHZ5nAG5wPZse8z5GfHo59dja8DJu/mjBF039X6B18HCs/wcP4pX0AinFUGlnzcIjMqAiwSyFNVQt0jWAC5Tz3+/dlsvAeZ8+suLUkvb72VGyBdarvSk8d0lz3Bctql+x/zOBi3RlVpYsLIkzDKSqgZPp3CK8OowW6YqVV7UxJmp4v97JWq9/6vg7TtLgKvCjjXSUjYTd1e8zFA5r6v/SnSbCmR1gc204wZ8urSTVYIBgEYhUO+ndbPDSzV3FYgbgx9bzg6dnt7A2+yC8wnF1VgvpE6al/T/gp1lGFpVl2Qb+2Us2ZB0sEuiODwUD8XQ2IcH5LMB3hsoOzCEhnEylTgQq5lO+OrIDLUX3NyCAR/E2FeOyFNtiTxY2w2VhOnsFpLMpuwaSML8KqacRPv0ccHrNnEk5N+cGywdK8jzAh6B9m4wAUypnoZoME9iRXazZCbKV2l86n872yHuIz5blOqmon5G4TTiqSJcqpUgYReqjkwC3oXdpJ6qZV64ZZJ5qd0yNgXg6m7BgLSzeU9+wnV4dibilCCOS78ZPAvmXdmKI9R1BIqE+A8xrjqezSZu9CUYh8yC4Rlv6Dtv8DoPVJXyTETwMJNhiQ0i6gX/pdHYb5kF8s5EHGR80QJlQRQFn6pd2Qo0IB7BJLSABAbF1IuA5dhd6T89ewVjwn8H8VVInyzwrdHOoW36ebQjH5AMJ27kRwDsRgwxE6WzWZZ+AJNL5aKNP0/tfc6AmAwmbLM3gB91IUBLZxVP9kPMfxg0eoL+U/AyeAhSls4P+ZVwZYyOeyrQpqmsL3Tpsi9kuP0eKNZL+KgKvg1yRLmmwDa3IGF37uek3kym4O3sU0tkdSGdXycZh0yDqlyb4QAmMyREJm0SRPBG5ovquPJOBMJ393459jAT0ckIhnZ3/7j67kgxN8AERxowAWwgkUaPMLqyhMAN8DSisUJlIZ1NIZ/NNpv94HVM8yuAJgZf4Zwp7EaQM2xYCyRaQRUZkHc8wLKta48vi6ewO2wjTvV46u4xhi4WrMpxk1S3DE8SO3jlSHGA6UDnQdFm8fpnvsnLKkFNGv4G6iXR20H+csCl7ZPnVzYMpexXsWBiRFBqXapJFQlZZKaHDXzmRzu5COjvanHNHTBR/GuAKN5B4YKRB6wiWtI5iKhtXg9+dPTsx+16vzTazk+zt4RwI024sWVhybOqhsj4LHOBf2pUFYROBNmCBdPaZW4699cTq2S30hP87kJFbEsLCdtim9xM2H/G+V5DdhrUoVPo3SgUFcXxo31WdwBb8AiLm09kr4OVfnWAt5UtcqmyqPIl0VoQjEqtUjm2bEoh+I+AF99KuCph0W2yibHKClK08nX2SjlxGjrIrCOuns1N1rNjlfWKKR+z+5wTqwN7HWS6QdBhnykNMOoGsTfF0djfYCPOgcJVqZnMdTpCpqKCw6T6QZV4VbvttBwNJB1FVgGYZrljGfOLB2xSseFcqT2fTR2euDCi7FJ5aekahSaJ+0cGtSFchSEEFHRgFqpSKq9rLCDxFiG9VBRWhF8k3RbBIfxFuDcd5Opud8j9GW8EmCKBzKquwyDahLSKMTe7/GHaeZxgckYRsaDggIliDqrpF8ruzg+dn301OB5/yxuhbIMHTksKkg6OYE0hh0FVJh226sArksgCeQztCf5IMpCYYZosTCIgdKO6ns9lPZq6mXvBuIL2Xzm4C1wPGKBRg22lL/4NdMFXYH3TYzvYr3S8nA8kWkAr9FTYp6jTD9vF0Nrty+pN0gn6EErZGT9pIgawinhREGmvCsZvqRwFPMAq9Rhi9kwX0C4dvmORvM9Twg6wKo1jEYMlRsSHWJp7O9pZKprMFnRYTW/9XS/jVPqcXMA0PzzxJlngPeszb+uJFE3sJvGc3qhqOSFX6EIXbKgAiS2Q+UcCKFfHbeo57x95xskOuokuC/wAL7OTvztaMTYwa6YipPlSFW5Ff1qJP0nG6bfxk9659162aTsNIXtqlj0rsS/EnVUlCWbxKRWLiojC+83T2cW/mSrjsuBQSCWcULrLDUFpVBiaHmLKq2oXRvu+z8Maj/XSc3O4d7Nz84q2rD4hE90YkHY4u0gjlw8R5PJ0Nhp3DLzsKXzeFREBlMYb7Pccl1A5VJkCgFuRG8yD/mH/TS/9r5ZOCmvPFbX7mlL7XjjfTBb4PSbpvpSvO20rCK9rKY3BPXiKd3aJvgdKW9LU7x47AoxQFUpUWOJL6hoAdQQR4uxTaUpUYhQvyOfIA/N86EUzufXYnnZMRUN6tkJxABC5BsAaiRHqly1PpbNLqp7P7AqQhS1eURiZVMcGvVAs9laTNl64oiTOPgDZ9En4V2rFkpHPHvq2D86A8DeUDiUvDNi4PoUXH+DuEuldO/zEdox+Bm40y09mJfsLmKSEcnxhpuNIVcTGimJ+F3SOH4M2Et7Dl/m1T16x8QQW1OJCyFKpoiNqgsBAJM/sZpbP9Y8EWCKJ/G2rH5kfGJJFOJG4TYkS6ZHCK6iQUiCqplavAhd/2TtKlrbtZ29968DMrHoc5joqYEHAvkJSblzBapEMjuQl0Iv2JSsmdeDqbeMGvwywoOQ8yhT0Ja3BPwbZBIQZKsnAicSglJtLP4AJuCf02/Kj6+aWvLtvz7HaYB/1ZNfvFI1I1ufKtI+PkWxipGU9nk6B/d3adWLF1S3meAaqx7SqC7JNnyKi3LeiSuw79+eTLRdVlj2cHkmnjZNFG9TQ6QSKdHZBzcueDtvMU8ZX1ybFr5DGhUgdPZbEzcoidZn/dXkVvOXDt5LMJfAg72YFUFiQCEDQReZ2WYxdPZ/s/OPZeuDt7i8fT2b7k3dkqwHNwqIhTbpPHlbJQgw3T+LN4pXBbT+DdG8yx6w+/fcWj8dt6MJGamyMVoc4ioahN1vGycmCV6pkPHfsVfne2N8Here1hI1lY02Vpx0gft2G/LL+6MGdxlSzz4YfxR8hI6wbv3yy79/AmeoJs1wUmumlVn3x5yUkS5NuVqZlygjCd/XA/nc0gne2nKsjKVmw2IB5LzoDgGgtM9GvavBbZT6bpdrKK3o45D0qrie9nX9rFa2B+10GqgvMl0tkjkM6uiqtqe0yOVWQpcCitRqfsNAiPvEaXercHM2THoZ295Q3pKrr27bm002VhJJc7O3Qq9dlvnPRbl4fpbNlVqlyGjmAx6WQRD1mfOmzL0qOrrNWbB5FX6faDv7zsn3TNg/Lg0zVXTh+kHlmbVynzmC1OkAkup9CDNAJPJNiA3yQG7GAxiT2nO8Gs77BTZFvrF5fvPsjnQTVt6pd2WR2ji9wsXaqE2RJEHD+mXap8qLYTYdfhA1m62r1l3uO/MPJ/Xtg0MaVqBlY79UDKQpBlcFa9ustEOE05QRX7dWCsgifdVsRtup7qfru/zNtfWOatKgqzXS+QAkyRCrI8hTY6muh2AgzMIowmA0yEQdW+IuxcXwsuyANY3jBHtk69bfIf6pgH5ZnXCyRbHDkLad1BnoUpXmYLd9jOHbdR93eOXRRM/NhS+iR7mdywdGX37nB5wxd0Ayovvw1xfqp8s1gL3Y6kW37MFPSvtp8EuMG28Js+EUBgsdPkeW8V+TItWOaN3m8KAqvPkZrgLHnE6HQknbLzbMI6VlPfBvC4K7gf+56ABjcc+u/Fy7yxzK0iRy6Q0meLKhqjtqKhPDpu6rMmZ9FqHlYAY8mRNbY/DwK1WyfGlu8NlzfItq25Xi+QdARKkWEinToCTKSrCKPouA6MIl0q5U08ObRgeUPb29b6+eDOA1etOKJidp1t5EYkkwixnV4HdhFGUwEm0l/FVlPYUxjBlCl47NLtdB27aeqaFS+kDjdmd2FEMkXkEDnBQC/rsG1AyRAUcJ4YOc7myC42RnYc3rbiu1WWedvAyMKI5JxArj+weTJ1AiuyDtsugT548HyHjJPH2BF63eSyyfvDeZDG5Q0CGOjFCyOSiuhF5gQqFBW24Q5sikcdwVIGe5s8Q+forWyU3n74S5MvHy4kpzkVFkYkFcyijilDrqxekS7Z9ul6OjCmdcjuY9smqxejngh7xC8cZ15vHtReDsu8ty5HX+aNYUZVGdUCSaRdRK6ofh3lIoyRA2BjEulT1aMLpyqedLuevcfJGP0a07zMO626jv1ql3aYiG1xDGyHx+QoLkuE0xSPIv19jJBE6C1veOPkbt3LvOO01PU9DCR43rEVmxBGQafZDd4wupq5AvXP0w69kUzCPOh6vMddGWaxtLpeICGQX+pB/AKYQhjCCBMIqqE4F6LQsBqAZqnMBZ/VYLAsoLC84Ti5na70dkzdaHaZ9yAa8yVoc6TCV5mYt01ao9aTAEeB4KjSxihUzIVXcBKAw53gJL2nRen2qd+sZ5m3gsnoTUoFkvqTkbNx23JJ2eSTQDazELu50ZFslRsrAjnweAISnCSPUEpvar918m/CZd63JeUupr0wkLADRJZAkV4dASbSJYs1q14aJ9eRLstqZ6JM58kBgvT5oMVubjP/yy/eIn6LnQk7bdHRG5GiU5Lg7GMarA6n12FD3Tix9ReeBOBxV944vTN8i92NzVjeoKPfs2S24XnIhLaiQwYjKQreSHXVT4PQ86BiO3eeLuxjHHs6mEJ7KOnQMfIgLALdOnFK/i122PhslpecI+m8HtDNAg/MMhODKniwTwIci40nAr4e6RR5At5K+IUly/rLvKvwNsRt+5d2FTzDlPPKdEKjTwQ8klL9oIvblJpMakfJVHCM7Bhpdb/44s1uHpTJUaywF0i6Oowrst25ddoeI7r4a4Z318GdT2ZgePzb4FRww0tfcvOg4n7r1UgmG+KtsC41rHHUuHEVvtfh3GXhqnDu0Q47zh70VlB4m3ezlnmXpUdH/eQcKa4h4wQZP2z1d6yTQJaRKk6aJaeusowTAbxJ9Sm4dNg+sSb4yr7ryr3Nuy4zbNMrHpFUkOp04DJ43ElAzFb8RDBKw2XeI2v4W+xWDOXyBjERuEfEIxKuHnxp2MGymE4CQW8exILhWOaN71zlJeIGErZzl7dHvYUIu44AE+lSRy+ZPmeEnaJ7CfX+9zAt865CG1bb8oFUmxNgmVxSjg57S0KQqp6FMzoJwDGYGj3v0daNZG3vLXbDtMxbih/NlXDnSKpg85xAVWaZdln6y7RP140cOF1uep8H0GnyCp0kXxwJ3+Y9nMu8TdOapW8wkCxygizAjSjjgWmKR/FJYCboenvoMnb9oQ3Ltb3NuxH9YQBkm3W9x6kXvB46fjTUZ4cTqJtuyoGLEIodvKhl5eOM0UegN29ov3my9xa7IXjcVWVSNAtoj17ENnV/5P09Y+yTlLC3hPpqdALN9uKKx+ap4kkA4MA8yLuRrDb3Nm9cQpsrbb7rXn/5q+u7k60PwXLhj9OAnFvapHlJpVu6BkUMFHDLOuQVuGH3Dr7M+9D1i2+ZdxF9Jo4PdNGaq2cuZEeDKyDNcxltk59DAYF95kYBlRKSZoJjTpelmtS+y8jpIPB2h8u837l4l3nX3g9CV4GL7DO3HHsrvKPmCsj7vA9eOThpvVOZZrPmkwOjNFzm7b25v8zbtP1OX4KB3HPu+ZvZ2MzszG8SFnwSRqd3QMteQiIhwtKdXMssxSwBCxbePc869K/C5Q23uuUNEpQZqSLlbud9+siKE0fbv0N8diWMUBfnIpOSmCvBHeQMxHnko5/XX94Ab7F7aadb3mCbk8S7qxBbIiHh5yQkSkktVCuuoOPyyhR2sVXpI6dZl+x1yxvStNi1r+Q2iYSEh5SQsIuXJBollpIiMvcKTwT0CdZlO9zyhkz2rCpUd5E7WevMh479CjtOr6Z+8C54gMpEJcvUkVRSa11j4AFu6/kZa9Fb3TzIut4RAqrsvgMJiQA5IVEZodD25IHC0SFZXWqvLPaWmwdJ8WphpbJdLTSBJySOH/J+j7Yp/w0qPyEhlDJkB2TZpSScB8HyhpvmlzcMGRXDbo5sV0vzwBMSHb/9R3SEfRRecnhufEGmSEjG6mdRValyGZ1SggxUgldBwjJv4pZ5G+Bapwr0QIrAxhMSUIZzh0QkXPFTR4CpnAQ4DsjE/YyeQe5oh8sb3DJvxS61ppm2QAothITE2j2zb/MZ28wTEvBbSLWEhDW0VQASwNu8W97X6AS7/vC25Y1/m3cFJoaqqd5A6lMVS0j8Cdxu9DbSIiPzLOqY5M8LR/iCxFA4CgUkucwbAZ4TYQcDSG4iZ0w8IeEH+hMS/Im7WjfZkwAjT3nj3pfYKre8QWt/1CjcaCBFdiYSEqfJuVG5lk+BhYEWZUmhEMjh8oaRN5Bt7nFXSW6GbU/gZmbMTCQkmB0JCRTLWW8e5JY3oLDZCCG1BlLIUDohQRUSErKXWLJdospKAAn/MfoEX+bd+sXlvWXesjpdvUYzoOoy6Eav28mW+v88sxGWbPQSEjSWkEDXpihQxBYP5LHeMm83D1LktuHNRK5Rm1lRQgIAbIa/iyDLx5cQ2Lt5bpm3vZ1jDpl1gRSZPp+Q8Nnl8Nqgs4wFk2wWAt5iB5ge9Fqtz025Zd5Rty3aT2sDKeqRn9909E20432MP0OCdO1ISNBRmAfBW+zcMu+ol9yn9YEUdlE6IREoJCQw+pq65Q0YNA6jjGYEUp/5gYQEM5CQ4ImEEXjs7wi9N3DLvIcxBlBsalQgRRav3zq9qvMk+0+wvxmSERdF5eif0TzIvcUOndphE9jIQIo6YSAhER3A+PTc8gYMGheLjEYHUtRJiYREJyMhUcZKNw+KaHWfJRgo42IlxNZQNZ2Q8GMJCRkrPXKcjtFdbh5UQ98NgUoZF2uUmQMJiaAgIeGFvwf9U+Itdo2y2IG1gYGhCyT4vYnCA+XZmj+deR076P8ukLyZ+IKERMvNg2xwwmHAMHyBlOqVREIigDsk+Oallnn3gy/V1O06BqQZGPpAipg4c/P0r4bP4AuC5eFtPbcue4iPXNFx9+kYqMLA/wf04ABkbIFlTAAAAABJRU5ErkJggg=="/> +<image id="image1_336_13394" width="600" height="315" xlink:href="data:image/png;base64,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"/> +<image id="image2_336_13394" width="512" height="512" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAAbFBMVEVHcExJsGSWvvHX3NahwfnwfHPrVEVonvXHxbvbxr9ct3PuaF30wCn1oIej17DykIdVk+7xh36KzJzx3JtywoZhu3n70VZbuXT80VaEsPaArff94I2wynY0qFPqQzX7vAVChfSowFX0izFbqLFJIxIpAAAAHXRSTlMA/XRMiNr93RIs9fH8YqmS+7fagfOv6lrFwJuqwgQzu7IAABDESURBVHja7J0JbuM6EEQJa8SQoASFMGA4E0AMdP9D/tk+nMVLnFhJkf3eFarUXd2kJAcAAAAAAAAAAAAAAAAAAAAA0BbhHw4sEMdxyrnvU0r+F/NLvPcppb7PeZrGiCfaIY5T7lPy81V4n/qcxxEn1Escc5/8/El+G2HCB3URp9yn+ab4lKfRgTxx6pOf1yL1uECXMOXn2q/pguhAizj1fv5CPKVAh4P4XwyV4PsJ4wXx168EDAjfRsxpFiBlusE3MGqo/xff44Ev5XfhVwMPOGdXfTzgnHX18cD6xKyt/h98ZjhchTAJpb7zJGZDc6WfVsDDTxlYiVjZw3+ANHADxgof/gOJTmCv9r8kTQ4+SKhh6ruMxwLGWv8bfCYPXi//3BRYwLT8WMC8/FjAvPzEwXcR8twynr2AhcGP1dAHmZqX/zeJBXGLS1/SINmPNEj2ow/Q/OkDVxPNNH9GwmNYq/70AaPZ/xjmw2CwlP0pAm8YDYY/igCPP0WAx58iwOP/jGRvJ8Djb3snYHf2P0V2hjC6+iMLGt78kwVJf5fpnQFsr35pA5T/87Q+DVD+TU8DpH/bSyGWP+/CtxoEphksBwHav+mNAO3f9kaA9m87CqK/7ShI/LN9QszZr+lhgMMf28NAIP6b3gsz/tl2QCT+mzYA4x/6A/qDzX0w6x/0B/SHj9FXfhqE/ugP6A/oD/b0H2dAfzCrP/s/0/pz/of+YHf/x/0P2/pz/8u2/rz+hf5gWH/u/9vWnwWwbf1ZANrWnwWAbf0ZAG3rzwBgXH8GANv6EwBt608AtK0/AdC2/hUHQP+H+QTo3+AG0KfU9zlP27Hr4m9CiL/ounE75dz36WAH9G8qAPrU52nsgrtE7KbcJ4/+7yToB0Cf8tQFdxUhbnOf0L/6AJD6g/bXE7c5efSvNQD4foru04QxJ4/+1QWAlDt3M8K2T+hfzwbg8OjfkC4n9K8iAPRTcH+Q9kAT+gs2gDRFtyZj79Ff9gjA586tTpgS+ks2gPS89GuXgTb015oA+9F9ITF78/orNQCfo/tiwuRt6+90JkCfg3uFsgUa0V+mAZyQX9YCjeiv0gD8FNw3ErK3qb/IBHD+6Ve0QCv6azSAXuLPWrG3p7/EJYDUORHGZEx/hQbglf6oFLI3pf84n8dE839JTIb0//4VQJJo/i+ZvBn9zyZAa9X/QExG9D+/ArCS/Y+RvQX9zyVAu4//X7pkQP9zCdBm939G6JvX/2ICNP4jtck3rv+FBMgf1bvUtP4XdoB8R9m50Desv8vzCSj/B3K7+p8oAJT/l0y+Uf1PFQDz6f81o29T/zgfhfb/hpha1P/EDoj2f9QBDerfzcewvfw7Seib0/9oASD+nXZAa/qP83fgZS7+XE1twUWyAFQW/1tmHObnoL81dqX4+YtJrVXRiunKL5b5H+hvjl155gD0N0dX/jLMv0B/e+zK/yzob5BYDizob499ecbA/GeNWF7i55Xx6C/FvrxiYf9riTCU1yyc/xjisRz4iiBQ5flvywzlGAv3P4wwluMsfEbfBrtygoEFgAViOY1nAGyffTnDwgDQOmEo5xgYABpnW84zEADbZlcusRAAGyaWyyxsgNtlX97BQABolqG8C08AaJNteScLG4AmOUTA1dvA1oEcobyfwdMAmuPQAdZuA54JUJFduYqFBtAWsVzJQANoisdyNZ4JoCF25XoWVkDNEMuBVdtAcqDIoQOs3Aa4BKDJrnyQhQTYArF8mIUE2ACP5eMMXAOvn135DAu3QConlM+xMALWzbZ8koERsGr25dN4RsCKGcrnWSgA1dKVW7BQAGplX27CQAGolF25EQsFoEZCuRkLBaBCtuV2DBSA+tiXW+IpALUxlJuysASsi1huzMBN4KrYllszcAxYEzeIAJfbAPcAdBnKCixcBKqFWFZhYAashG1ZCc8MWAX7shYLM2AN7MpqDERAfcJQVoQIKE9XVmUhAorzWFZ3gHegy76szEAHkGYoq0MHECaU1Rkc6NKV1dk7Cbq7tnnQzIA6HWDz1DiiGbAUp0HzBohqe0CtDtC+ATaiQ4DKR+GaN8BPobPg56jcBWveAHdSZ8F6QyAGOMajmQjQvgHuNYcAkSHQgAGeguQQ4FRo3wAbxSFg51Ro3wA/FE8CZCKAAQM8KJ4EyEQAAwa4U5wCnQwY4AiPZrYAFgxwLzgF6kQAAwZ4EpwCVQ4CbBgg6k2BQi8EGDDARs8ATgcDBvgptwYQyoAWDPAgdxgslAEtGOBObg3w6HTAAG/Z2tkDmjDAvdweSGgIwABH2BsaAiwY4EnNAEpDgAkDBLFFoM5lACMG6MQMoDQFmjDARmwRiAFeIncnaLBzFGTDAD/FDKC0BjBhgAexowClNQAGeEu08laYGQPciRnAKYEB3tBZ2gNhAAzQPvfuKkYM0Bj3WqfBUptgDIABDKBlAKlNMAbAAAYIGAAD6NwIwwCvEXs3iArQHJEKYNsAHRUAA1ABMAAVwKoBNhgAA2AADIABMACHQRjgIlvuA7TGhhtBGAADGDZAx61g2wbYYADbBui03gzixZADkgYYeDWsMSIvh57kx5MBxAzA6+EHJA1g6gshJipAwAC2K4DYN4KkNkEmKgCfibNdAe7VvhTqhLBQAeQMoLQIoAIY/1i0hQpwp/bbQKUxwEIFuOOHEVQAqcMApTHAQgV44KdRp/n51D4Pcv8NFBoDqADfYYDJyWChAvyQ+2eMUAq0EAI3cgYQSoEWKkAn99swoVthFipAlPtzpNAu0EIFCE5tESAUAgxUgHun9/donRBgoALcOyc3B+qsggxUgDvn1P4cKBQCDFSAB+fk5kCdEPBfO2e7mzoPBGGbVMKxjqKIj6LDq0itcv/3+Ib2tFsaEsCxYbye5y8/d9mZ2TUUMAFODYB2EMYxAQVMgKMxeDkQZhNQQAOsDGAMgPl1SAENUBnEGICiAQU0gDOIMQAlCOpvgNYM4F0DUIKg/gZYG4MYA1CCIBtg4hpQigbob4C/xkC6wHeMd2H6G+DFGEQX+N41Zh42QLwUCLcM/uO7zhsE6pdnswZMgald4LvtBjA04Om0gCkwsQt87z7YGzKAGALSukDfnUDRgGdTQYaAhCbgj+2+OBhijni3wKS7wPdO2Bhi1oC3wBN1uvoLKDfhSfL3gG/OAJmAIf39gzbwHw7xFJRiFSTp7wRt4Bcr0BCQwgTI+KcN/GYNGgISmADfjdmZ0mkhLwHxTYCkP4HbQGPqN7TfBcY3ATL+OQJGHGE9oDHb2PXnCBizhvWAxtTB54D59MckKDjM1yBxTcA4/XEZ9MUK8zVItHOAjH+OgIusgT2gcVWU+vtuHlvwCHCQT8JjvQmQ9McRMMER8kVweBCcH/8MAiPWgL8LjRUEpf4cAVM40AehEYKgpD+OgGmO2BZgLgguTH88Cn7QgluAURCMNP55FPxkBfqjoMUaIOmPI2CONeRfQ8xrQED6ow+cOQSiPgcbacDi8c9t0Ii/mL8LPqf6E1x/joB5HOrvgsM1YJz+6AOnOaL+LHSkAeHpjz5wGtdi/jfMMg2Q8U8RuMIR9K9BRrwuT3/cB14eAOCHANGA+9MfReCWAYD8FiBgFyTjnyJwSwTIwwIMvN5bf4rAdf7C/jnUmO2d6Y8iALEElD3wUtzrPemPIoBxBRAFWM5/geOf66CZPwXJJQTKKiAs/fEmMPUOAPTvAcNsoKQ/2gCQCCiXwAhsbx7/tAEgEVAUIAb16231pw2AcYCiAMltoKQ/2gCQh2CiAA+wgZL+aANgjgCiAKltoIx/2gAoARAFiMN2Ov2xAxAFQBQgzTZwnP5oBHF2wKIAaW3ge5cMq7UDPg1APneAURKcrD+jAMoRUO4AaUaApL+keI0d8GUAsrkEj5LgdPpjGMQxAPIcOMEIkPHPDsDcAIgCpBoB4/HPMAi0ARg4mhS8/kx/7ABYAzjgTKIRIOOfHYB5A5YlQLIRsOs+YAeABgBZAiQZATL+2QFwj8DEAqbBvZ6Nf3YAYgAUC5iA1Xn92QG3Urdvj6N1Jhlj/ec+AK3+sgVMwKq7DLfCOPU/bQGVjYDML0Pn9c82A94wAngdvl7/fDPg1AhgB+DkP8mA6ai6izAM3Lr/yewt4JhNN4IdcPP+N+sM+Ellu0swDEDU/7QEUjsCsjMC7uz+q2QAGFNPjQDKwDX7n/cS6JumuwxlYM7+qRkAxjjfXYYyMCf/agbA7DaIMvBJPSH/WfwvHLIPzGQIXBj/igbAfBTkELjo/vU4gGs+kENg3/m3EaoGgPjA57FHjQMfKdn+SoDaBsAVH1jyENh3n/RvZygbAOIDn4nHa4GDmCP/JugbADftA4vTgVqEcSQDKq4AUD4QLg+4fXeO7aU6+b8DgHkaAmoFnJRfkA7I/iEQ4DIAqgXOyz82Apm/BEQWAYAWcBPlFyOQ+VPgCRyGCAS0QOLyCyIDqiIgmgicsE+yg4f58j+oA9raXKAgEXhWKDz47jq2VRcBAUXgCasht791AvbqIiCiCHxgHzUG3MF3t9Nri4CgIiBj4LnKP8brc4A4N4Ex+wNU9U/YVp0D/N5/I2L3B4DJf0af7W+B0A/D01rgTFQOv1wfhgyszRylioAMAher+L47B0UGKnOVsrLgb/zSJqjlm7+UXpkDBHoaMI8N6wJ3kC9+HHp1AnBi2+WB3+8P9c2V3yfpa69NAOBtwAjr/X7ohMPBOWe+ca4+DAyfeJt0otlWmQDkYAOw6JUJAPA2AJRoMtBiCADkUQAa2+Z9BMzkKIBMr0oA8jOCc2TTAW1tkKARvA+f8xGYRjACtlWRAGkEw+n1GAAawSC8GgPADgjDtloMAKNAIL0WA8AoEEivxQCwAwLx8H8FwDCYFtvnewJgGIxCn9UrUHZAfLwGA/jNih1wN7ZVYAC5DlhCf2v9gQ0gO2AJfcYbQHZADHz2AYAdsAzb57oBZgdEos87ALIDFuPzDoA8DC3Gtlrqzw4IpNdSf6pAIH3GCwB2QAxsq6T+7IBAbK+k/uyAUHol9Tem4WUoCK+k/rwNhmJbHfXn+4Bgeh31N6biK7Eweh31N6bmS9EwbKui/nwrHIztVdTfGMe1cCD57X+5EIhJY9SwZRi4G6uo/gwD92Pzef/DMJAAn8v7P1rBJOzyeP/Ly0AiNiriH41AKBujFBqB8uz/OY4bgQLtHw/Exds/ykDp9o8yQPlnGrjOTrf8CzWXQoWOfy6Fih//9IKlun8OAX79OQSKd38cAhdpCnJ/jAOl7X6v0JS+E7Dlfv05BApW/59U5ZrBIs3/GFeqGdwUl/2pAz/wyt79Ugdo/pgHOP1pBej9l1iBIlrAbw0p1w16iv8sle4WoPcrugVsQ+9XcAuw/EW3AMt/H5WuROBZ/pJDIZ1/GLWO7eBuy/KH4prcbwR2w61fwWaA0h+DOtcxwNkfjVV+Y8A3nP3ljgG74Zc/hRvIJBTsqPyJcBlIwY6jPylui9wDrP4jqF8gtcCy+o/DrcB6wG5Y/QfjqmYHIga7zZau7ynUzdMHgedX/7m46hlNIMVn3J9FbRPYHYuPxakJdv5RtV9R8yGpP7qAtS8bVzXNJnZA8EPpm4q1z4e6ehnaYHEf2KHym2ZFuc+VuloN82Cz8/beup8K/1Kx8kpwdXVqhWYzNMOAH7BW8H53Yvi0aZpVVdWsu3Kcc7XgHAtOCCGEEEIIIYQQQgghhBBCCCGEEEJy538RWcp2BQX42QAAAABJRU5ErkJggg=="/> +<image id="image3_336_13394" width="300" height="300" xlink:href="data:image/png;base64,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"/> +<image id="image4_336_13394" width="512" height="512" xlink:href="data:image/png;base64,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"/> +<image id="image5_336_13394" width="512" height="512" xlink:href="data:image/png;base64,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"/> +<image id="image6_336_13394" width="512" height="512" xlink:href="data:image/png;base64,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"/> +</defs> +</svg> + From ecd4f0a7eccc69949f017eb389c9b825472cb258 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Thu, 18 Jan 2024 11:30:53 -0500 Subject: [PATCH 084/215] core[patch]: testing add chat model for unit-tests (#16209) This PR adds a fake chat model for testing purposes. Used in this PR: https://github.com/langchain-ai/langchain/pull/16172 --- libs/core/tests/unit_tests/fake/chat_model.py | 193 +++++++++++++++++- .../unit_tests/fake/test_fake_chat_model.py | 184 +++++++++++++++++ 2 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 libs/core/tests/unit_tests/fake/test_fake_chat_model.py diff --git a/libs/core/tests/unit_tests/fake/chat_model.py b/libs/core/tests/unit_tests/fake/chat_model.py index 717ab02533f37..98f05b6ca6060 100644 --- a/libs/core/tests/unit_tests/fake/chat_model.py +++ b/libs/core/tests/unit_tests/fake/chat_model.py @@ -1,15 +1,21 @@ -"""Fake ChatModel for testing purposes.""" +"""Fake Chat Model wrapper for testing purposes.""" import asyncio +import re import time -from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Union +from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Union, cast from langchain_core.callbacks.manager import ( AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun, ) from langchain_core.language_models.chat_models import BaseChatModel, SimpleChatModel -from langchain_core.messages import AIMessageChunk, BaseMessage +from langchain_core.messages import ( + AIMessage, + AIMessageChunk, + BaseMessage, +) from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult +from langchain_core.runnables import run_in_executor class FakeMessagesListChatModel(BaseChatModel): @@ -114,3 +120,184 @@ async def _astream( @property def _identifying_params(self) -> Dict[str, Any]: return {"responses": self.responses} + + +class FakeChatModel(SimpleChatModel): + """Fake Chat Model wrapper for testing purposes.""" + + def _call( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + return "fake response" + + async def _agenerate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + output_str = "fake response" + message = AIMessage(content=output_str) + generation = ChatGeneration(message=message) + return ChatResult(generations=[generation]) + + @property + def _llm_type(self) -> str: + return "fake-chat-model" + + @property + def _identifying_params(self) -> Dict[str, Any]: + return {"key": "fake"} + + +class GenericFakeChatModel(BaseChatModel): + """A generic fake chat model that can be used to test the chat model interface. + + * Chat model should be usable in both sync and async tests + * Invokes on_llm_new_token to allow for testing of callback related code for new + tokens. + * Includes logic to break messages into message chunk to facilitate testing of + streaming. + """ + + messages: Iterator[AIMessage] + """Get an iterator over messages. + + This can be expanded to accept other types like Callables / dicts / strings + to make the interface more generic if needed. + + Note: if you want to pass a list, you can use `iter` to convert it to an iterator. + + Please note that streaming is not implemented yet. We should try to implement it + in the future by delegating to invoke and then breaking the resulting output + into message chunks. + """ + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + """Top Level call""" + message = next(self.messages) + generation = ChatGeneration(message=message) + return ChatResult(generations=[generation]) + + def _stream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[ChatGenerationChunk]: + """Stream the output of the model.""" + chat_result = self._generate( + messages, stop=stop, run_manager=run_manager, **kwargs + ) + if not isinstance(chat_result, ChatResult): + raise ValueError( + f"Expected generate to return a ChatResult, " + f"but got {type(chat_result)} instead." + ) + + message = chat_result.generations[0].message + + if not isinstance(message, AIMessage): + raise ValueError( + f"Expected invoke to return an AIMessage, " + f"but got {type(message)} instead." + ) + + content = message.content + + if content: + # Use a regular expression to split on whitespace with a capture group + # so that we can preserve the whitespace in the output. + assert isinstance(content, str) + content_chunks = cast(List[str], re.split(r"(\s)", content)) + + for token in content_chunks: + chunk = ChatGenerationChunk(message=AIMessageChunk(content=token)) + yield chunk + if run_manager: + run_manager.on_llm_new_token(token, chunk=chunk) + + if message.additional_kwargs: + for key, value in message.additional_kwargs.items(): + # We should further break down the additional kwargs into chunks + # Special case for function call + if key == "function_call": + for fkey, fvalue in value.items(): + if isinstance(fvalue, str): + # Break function call by `,` + fvalue_chunks = cast(List[str], re.split(r"(,)", fvalue)) + for fvalue_chunk in fvalue_chunks: + chunk = ChatGenerationChunk( + message=AIMessageChunk( + content="", + additional_kwargs={ + "function_call": {fkey: fvalue_chunk} + }, + ) + ) + yield chunk + if run_manager: + run_manager.on_llm_new_token( + "", + chunk=chunk, # No token for function call + ) + else: + chunk = ChatGenerationChunk( + message=AIMessageChunk( + content="", + additional_kwargs={"function_call": {fkey: fvalue}}, + ) + ) + yield chunk + if run_manager: + run_manager.on_llm_new_token( + "", + chunk=chunk, # No token for function call + ) + else: + chunk = ChatGenerationChunk( + message=AIMessageChunk( + content="", additional_kwargs={key: value} + ) + ) + yield chunk + if run_manager: + run_manager.on_llm_new_token( + "", + chunk=chunk, # No token for function call + ) + + async def _astream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> AsyncIterator[ChatGenerationChunk]: + """Stream the output of the model.""" + result = await run_in_executor( + None, + self._stream, + messages, + stop=stop, + run_manager=run_manager.get_sync() if run_manager else None, + **kwargs, + ) + for chunk in result: + yield chunk + + @property + def _llm_type(self) -> str: + return "generic-fake-chat-model" diff --git a/libs/core/tests/unit_tests/fake/test_fake_chat_model.py b/libs/core/tests/unit_tests/fake/test_fake_chat_model.py new file mode 100644 index 0000000000000..8700f0751caa3 --- /dev/null +++ b/libs/core/tests/unit_tests/fake/test_fake_chat_model.py @@ -0,0 +1,184 @@ +"""Tests for verifying that testing utility code works as expected.""" +from itertools import cycle +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from langchain_core.callbacks.base import AsyncCallbackHandler +from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage +from langchain_core.outputs import ChatGenerationChunk, GenerationChunk +from tests.unit_tests.fake.chat_model import GenericFakeChatModel + + +def test_generic_fake_chat_model_invoke() -> None: + # Will alternate between responding with hello and goodbye + infinite_cycle = cycle([AIMessage(content="hello"), AIMessage(content="goodbye")]) + model = GenericFakeChatModel(messages=infinite_cycle) + response = model.invoke("meow") + assert response == AIMessage(content="hello") + response = model.invoke("kitty") + assert response == AIMessage(content="goodbye") + response = model.invoke("meow") + assert response == AIMessage(content="hello") + + +async def test_generic_fake_chat_model_ainvoke() -> None: + # Will alternate between responding with hello and goodbye + infinite_cycle = cycle([AIMessage(content="hello"), AIMessage(content="goodbye")]) + model = GenericFakeChatModel(messages=infinite_cycle) + response = await model.ainvoke("meow") + assert response == AIMessage(content="hello") + response = await model.ainvoke("kitty") + assert response == AIMessage(content="goodbye") + response = await model.ainvoke("meow") + assert response == AIMessage(content="hello") + + +async def test_generic_fake_chat_model_stream() -> None: + """Test streaming.""" + infinite_cycle = cycle( + [ + AIMessage(content="hello goodbye"), + ] + ) + model = GenericFakeChatModel(messages=infinite_cycle) + chunks = [chunk async for chunk in model.astream("meow")] + assert chunks == [ + AIMessageChunk(content="hello"), + AIMessageChunk(content=" "), + AIMessageChunk(content="goodbye"), + ] + + chunks = [chunk for chunk in model.stream("meow")] + assert chunks == [ + AIMessageChunk(content="hello"), + AIMessageChunk(content=" "), + AIMessageChunk(content="goodbye"), + ] + + # Test streaming of additional kwargs. + # Relying on insertion order of the additional kwargs dict + message = AIMessage(content="", additional_kwargs={"foo": 42, "bar": 24}) + model = GenericFakeChatModel(messages=cycle([message])) + chunks = [chunk async for chunk in model.astream("meow")] + assert chunks == [ + AIMessageChunk(content="", additional_kwargs={"foo": 42}), + AIMessageChunk(content="", additional_kwargs={"bar": 24}), + ] + + message = AIMessage( + content="", + additional_kwargs={ + "function_call": { + "name": "move_file", + "arguments": '{\n "source_path": "foo",\n "' + 'destination_path": "bar"\n}', + } + }, + ) + model = GenericFakeChatModel(messages=cycle([message])) + chunks = [chunk async for chunk in model.astream("meow")] + + assert chunks == [ + AIMessageChunk( + content="", additional_kwargs={"function_call": {"name": "move_file"}} + ), + AIMessageChunk( + content="", + additional_kwargs={ + "function_call": {"arguments": '{\n "source_path": "foo"'} + }, + ), + AIMessageChunk( + content="", additional_kwargs={"function_call": {"arguments": ","}} + ), + AIMessageChunk( + content="", + additional_kwargs={ + "function_call": {"arguments": '\n "destination_path": "bar"\n}'} + }, + ), + ] + + accumulate_chunks = None + for chunk in chunks: + if accumulate_chunks is None: + accumulate_chunks = chunk + else: + accumulate_chunks += chunk + + assert accumulate_chunks == AIMessageChunk( + content="", + additional_kwargs={ + "function_call": { + "name": "move_file", + "arguments": '{\n "source_path": "foo",\n "' + 'destination_path": "bar"\n}', + } + }, + ) + + +async def test_generic_fake_chat_model_astream_log() -> None: + """Test streaming.""" + infinite_cycle = cycle([AIMessage(content="hello goodbye")]) + model = GenericFakeChatModel(messages=infinite_cycle) + log_patches = [ + log_patch async for log_patch in model.astream_log("meow", diff=False) + ] + final = log_patches[-1] + assert final.state["streamed_output"] == [ + AIMessageChunk(content="hello"), + AIMessageChunk(content=" "), + AIMessageChunk(content="goodbye"), + ] + + +async def test_callback_handlers() -> None: + """Verify that model is implemented correctly with handlers working.""" + + class MyCustomAsyncHandler(AsyncCallbackHandler): + def __init__(self, store: List[str]) -> None: + self.store = store + + async def on_chat_model_start( + self, + serialized: Dict[str, Any], + messages: List[List[BaseMessage]], + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Any: + # Do nothing + # Required to implement since this is an abstract method + pass + + async def on_llm_new_token( + self, + token: str, + *, + chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + **kwargs: Any, + ) -> None: + self.store.append(token) + + infinite_cycle = cycle( + [ + AIMessage(content="hello goodbye"), + ] + ) + model = GenericFakeChatModel(messages=infinite_cycle) + tokens: List[str] = [] + # New model + results = list(model.stream("meow", {"callbacks": [MyCustomAsyncHandler(tokens)]})) + assert results == [ + AIMessageChunk(content="hello"), + AIMessageChunk(content=" "), + AIMessageChunk(content="goodbye"), + ] + assert tokens == ["hello", " ", "goodbye"] From 6b9e3ed9e9267e60ec30f571ed0486d936c840cc Mon Sep 17 00:00:00 2001 From: Eugene Zapolsky <eugene.zapolsky@gmail.com> Date: Thu, 18 Jan 2024 18:54:30 +0200 Subject: [PATCH 085/215] google-vertexai[minor]: added safety_settings property to gemini wrapper (#15344) **Description:** Gemini model has quite annoying default safety_settings settings. In addition, current VertexAI class doesn't provide a property to override such settings. So, this PR aims to - add safety_settings property to VertexAI - fix issue with incorrect LLM output parsing when LLM responds with appropriate 'blocked' response - fix issue with incorrect parsing LLM output when Gemini API blocks prompt itself as inappropriate - add safety_settings related tests I'm not enough familiar with langchain code base and guidelines. So, any comments and/or suggestions are very welcome. **Issue:** it will likely fix #14841 --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- .../chat/google_vertex_ai_palm.ipynb | 179 +++++++++++++++--- .../langchain_google_vertexai/__init__.py | 10 +- .../langchain_google_vertexai/_enums.py | 6 + .../langchain_google_vertexai/_utils.py | 28 ++- .../langchain_google_vertexai/chat_models.py | 55 ++++-- .../langchain_google_vertexai/llms.py | 59 ++++-- libs/partners/google-vertexai/poetry.lock | 94 ++++----- .../integration_tests/test_chat_models.py | 12 +- .../tests/integration_tests/test_llms.py | 5 + .../integration_tests/test_llms_safety.py | 97 ++++++++++ .../tests/unit_tests/test_imports.py | 9 +- 11 files changed, 448 insertions(+), 106 deletions(-) create mode 100644 libs/partners/google-vertexai/langchain_google_vertexai/_enums.py create mode 100644 libs/partners/google-vertexai/tests/integration_tests/test_llms_safety.py diff --git a/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb b/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb index 551e1c8df08bf..0443dbf844285 100644 --- a/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb +++ b/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb @@ -35,7 +35,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": { "tags": [] }, @@ -44,10 +44,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Note: you may need to restart the kernel to use updated packages.\n" + "^C\n", + "\u001b[31mERROR: Operation cancelled by user\u001b[0m\u001b[31m\n", + "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" ] } ], @@ -57,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -76,7 +75,7 @@ "AIMessage(content=\" J'aime la programmation.\")" ] }, - "execution_count": 2, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -101,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -110,7 +109,7 @@ "AIMessage(content=' プログラミングが大好きです')" ] }, - "execution_count": 3, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -154,7 +153,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "tags": [] }, @@ -165,27 +164,51 @@ "text": [ " ```python\n", "def is_prime(n):\n", - " if n <= 1:\n", - " return False\n", - " for i in range(2, n):\n", - " if n % i == 0:\n", - " return False\n", - " return True\n", + " \"\"\"\n", + " Check if a number is prime.\n", + "\n", + " Args:\n", + " n: The number to check.\n", + "\n", + " Returns:\n", + " True if n is prime, False otherwise.\n", + " \"\"\"\n", + "\n", + " # If n is 1, it is not prime.\n", + " if n == 1:\n", + " return False\n", + "\n", + " # Iterate over all numbers from 2 to the square root of n.\n", + " for i in range(2, int(n ** 0.5) + 1):\n", + " # If n is divisible by any number from 2 to its square root, it is not prime.\n", + " if n % i == 0:\n", + " return False\n", + "\n", + " # If n is divisible by no number from 2 to its square root, it is prime.\n", + " return True\n", + "\n", "\n", "def find_prime_numbers(n):\n", - " prime_numbers = []\n", - " for i in range(2, n + 1):\n", - " if is_prime(i):\n", - " prime_numbers.append(i)\n", - " return prime_numbers\n", + " \"\"\"\n", + " Find all prime numbers up to a given number.\n", + "\n", + " Args:\n", + " n: The upper bound for the prime numbers to find.\n", + "\n", + " Returns:\n", + " A list of all prime numbers up to n.\n", + " \"\"\"\n", "\n", - "print(find_prime_numbers(100))\n", - "```\n", + " # Create a list of all numbers from 2 to n.\n", + " numbers = list(range(2, n + 1))\n", "\n", - "Output:\n", + " # Iterate over the list of numbers and remove any that are not prime.\n", + " for number in numbers:\n", + " if not is_prime(number):\n", + " numbers.remove(number)\n", "\n", - "```\n", - "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]\n", + " # Return the list of prime numbers.\n", + " return numbers\n", "```\n" ] } @@ -199,6 +222,102 @@ "print(message.content)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Full generation info\n", + "\n", + "We can use the `generate` method to get back extra metadata like [safety attributes](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/responsible-ai#safety_attribute_confidence_scoring) and not just chat completions\n", + "\n", + "Note that the `generation_info` will be different depending if you're using a gemini model or not.\n", + "\n", + "### Gemini model\n", + "\n", + "`generation_info` will include:\n", + "\n", + "- `is_blocked`: whether generation was blocked or not\n", + "- `safety_ratings`: safety ratings' categories and probability labels" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'is_blocked': False,\n", + " 'safety_ratings': [{'category': 'HARM_CATEGORY_HARASSMENT',\n", + " 'probability_label': 'NEGLIGIBLE'},\n", + " {'category': 'HARM_CATEGORY_HATE_SPEECH',\n", + " 'probability_label': 'NEGLIGIBLE'},\n", + " {'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',\n", + " 'probability_label': 'NEGLIGIBLE'},\n", + " {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',\n", + " 'probability_label': 'NEGLIGIBLE'}]}\n" + ] + } + ], + "source": [ + "from pprint import pprint\n", + "\n", + "from langchain_core.messages import HumanMessage\n", + "from langchain_google_vertexai import ChatVertexAI, HarmBlockThreshold, HarmCategory\n", + "\n", + "human = \"Translate this sentence from English to French. I love programming.\"\n", + "messages = [HumanMessage(content=human)]\n", + "\n", + "\n", + "chat = ChatVertexAI(\n", + " model_name=\"gemini-pro\",\n", + " safety_settings={\n", + " HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE\n", + " },\n", + ")\n", + "\n", + "result = chat.generate([messages])\n", + "pprint(result.generations[0][0].generation_info)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Non-gemini model\n", + "\n", + "`generation_info` will include:\n", + "\n", + "- `is_blocked`: whether generation was blocked or not\n", + "- `safety_attributes`: a dictionary mapping safety attributes to their scores" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'is_blocked': False,\n", + " 'safety_attributes': {'Derogatory': 0.1,\n", + " 'Finance': 0.3,\n", + " 'Insult': 0.1,\n", + " 'Sexual': 0.1}}\n" + ] + } + ], + "source": [ + "chat = ChatVertexAI() # default is `chat-bison`\n", + "\n", + "result = chat.generate([messages])\n", + "pprint(result.generations[0][0].generation_info)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -210,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -224,7 +343,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -268,7 +387,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [ { diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py b/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py index 391a7c7b1d1e0..ba97adf52e839 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py @@ -1,5 +1,13 @@ +from langchain_google_vertexai._enums import HarmBlockThreshold, HarmCategory from langchain_google_vertexai.chat_models import ChatVertexAI from langchain_google_vertexai.embeddings import VertexAIEmbeddings from langchain_google_vertexai.llms import VertexAI, VertexAIModelGarden -__all__ = ["ChatVertexAI", "VertexAIEmbeddings", "VertexAI", "VertexAIModelGarden"] +__all__ = [ + "ChatVertexAI", + "VertexAIEmbeddings", + "VertexAI", + "VertexAIModelGarden", + "HarmBlockThreshold", + "HarmCategory", +] diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/_enums.py b/libs/partners/google-vertexai/langchain_google_vertexai/_enums.py new file mode 100644 index 0000000000000..00a2abaa32106 --- /dev/null +++ b/libs/partners/google-vertexai/langchain_google_vertexai/_enums.py @@ -0,0 +1,6 @@ +from vertexai.preview.generative_models import ( # type: ignore + HarmBlockThreshold, + HarmCategory, +) + +__all__ = ["HarmBlockThreshold", "HarmCategory"] diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/_utils.py b/libs/partners/google-vertexai/langchain_google_vertexai/_utils.py index 6dcc7a2d73cd8..340acc05d8ff6 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/_utils.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/_utils.py @@ -1,6 +1,6 @@ """Utilities to init Vertex AI.""" from importlib import metadata -from typing import Any, Callable, Optional, Union +from typing import Any, Callable, Dict, Optional, Union import google.api_core from google.api_core.gapic_v1.client_info import ClientInfo @@ -86,3 +86,29 @@ def is_codey_model(model_name: str) -> bool: def is_gemini_model(model_name: str) -> bool: """Returns True if the model name is a Gemini model.""" return model_name is not None and "gemini" in model_name + + +def get_generation_info(candidate: Any, is_gemini: bool) -> Optional[Dict[str, Any]]: + try: + if is_gemini: + # https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini#response_body + return { + "is_blocked": any( + [rating.blocked for rating in candidate.safety_ratings] + ), + "safety_ratings": [ + { + "category": rating.category.name, + "probability_label": rating.probability.name, + } + for rating in candidate.safety_ratings + ], + } + else: + # https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text-chat#response_body + return { + "is_blocked": candidate.is_blocked, + "safety_attributes": candidate.safety_attributes, + } + except Exception: + return None diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py b/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py index 72f23815d7d7f..a76ade2d227d9 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py @@ -47,6 +47,9 @@ ) from langchain_google_vertexai._utils import ( + get_generation_info, + is_codey_model, + is_gemini_model, load_image_from_gcs, ) from langchain_google_vertexai.functions_utils import ( @@ -54,8 +57,6 @@ ) from langchain_google_vertexai.llms import ( _VertexAICommon, - is_codey_model, - is_gemini_model, ) logger = logging.getLogger(__name__) @@ -271,9 +272,16 @@ def get_lc_namespace(cls) -> List[str]: def validate_environment(cls, values: Dict) -> Dict: """Validate that the python package exists in environment.""" is_gemini = is_gemini_model(values["model_name"]) + safety_settings = values["safety_settings"] + + if safety_settings and not is_gemini: + raise ValueError("Safety settings are only supported for Gemini models") + cls._init_vertexai(values) if is_gemini: - values["client"] = GenerativeModel(model_name=values["model_name"]) + values["client"] = GenerativeModel( + model_name=values["model_name"], safety_settings=safety_settings + ) else: if is_codey_model(values["model_name"]): model_cls = CodeChatModel @@ -306,6 +314,7 @@ def _generate( ValueError: if the last message in the list is not from human. """ should_stream = stream if stream is not None else self.streaming + safety_settings = kwargs.pop("safety_settings", None) if should_stream: stream_iter = self._stream( messages, stop=stop, run_manager=run_manager, **kwargs @@ -325,9 +334,17 @@ def _generate( # set param to `functions` until core tool/function calling implemented raw_tools = params.pop("functions") if "functions" in params else None tools = _format_tools_to_vertex_tool(raw_tools) if raw_tools else None - response = chat.send_message(message, generation_config=params, tools=tools) + response = chat.send_message( + message, + generation_config=params, + tools=tools, + safety_settings=safety_settings, + ) generations = [ - ChatGeneration(message=_parse_response_candidate(c)) + ChatGeneration( + message=_parse_response_candidate(c), + generation_info=get_generation_info(c, self._is_gemini_model), + ) for c in response.candidates ] else: @@ -339,7 +356,10 @@ def _generate( chat = self._start_chat(history, **params) response = chat.send_message(question.content, **msg_params) generations = [ - ChatGeneration(message=AIMessage(content=r.text)) + ChatGeneration( + message=AIMessage(content=r.text), + generation_info=get_generation_info(r, self._is_gemini_model), + ) for r in response.candidates ] return ChatResult(generations=generations) @@ -370,6 +390,7 @@ async def _agenerate( logger.warning("ChatVertexAI does not currently support async streaming.") params = self._prepare_params(stop=stop, **kwargs) + safety_settings = kwargs.pop("safety_settings", None) msg_params = {} if "candidate_count" in params: msg_params["candidate_count"] = params.pop("candidate_count") @@ -382,22 +403,31 @@ async def _agenerate( raw_tools = params.pop("functions") if "functions" in params else None tools = _format_tools_to_vertex_tool(raw_tools) if raw_tools else None response = await chat.send_message_async( - message, generation_config=params, tools=tools + message, + generation_config=params, + tools=tools, + safety_settings=safety_settings, ) generations = [ - ChatGeneration(message=_parse_response_candidate(c)) + ChatGeneration( + message=_parse_response_candidate(c), + generation_info=get_generation_info(c, self._is_gemini_model), + ) for c in response.candidates ] else: question = _get_question(messages) history = _parse_chat_history(messages[:-1]) - examples = kwargs.get("examples", None) + examples = kwargs.get("examples", None) or self.examples if examples: params["examples"] = _parse_examples(examples) chat = self._start_chat(history, **params) response = await chat.send_message_async(question.content, **msg_params) generations = [ - ChatGeneration(message=AIMessage(content=r.text)) + ChatGeneration( + message=AIMessage(content=r.text), + generation_info=get_generation_info(r, self._is_gemini_model), + ) for r in response.candidates ] return ChatResult(generations=generations) @@ -441,7 +471,10 @@ def _stream( for response in responses: if run_manager: run_manager.on_llm_new_token(response.text) - yield ChatGenerationChunk(message=AIMessageChunk(content=response.text)) + yield ChatGenerationChunk( + message=AIMessageChunk(content=response.text), + generation_info=get_generation_info(response, self._is_gemini_model), + ) def _start_chat( self, history: _ChatHistory, **kwargs: Any diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py index 6e02ae43f981c..a71e6a0736eae 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py @@ -26,7 +26,10 @@ from vertexai.language_models._language_models import ( # type: ignore TextGenerationResponse, ) -from vertexai.preview.generative_models import GenerativeModel, Image # type: ignore +from vertexai.preview.generative_models import ( # type: ignore + GenerativeModel, + Image, +) from vertexai.preview.language_models import ( # type: ignore CodeGenerationModel as PreviewCodeGenerationModel, ) @@ -34,9 +37,11 @@ TextGenerationModel as PreviewTextGenerationModel, ) +from langchain_google_vertexai._enums import HarmBlockThreshold, HarmCategory from langchain_google_vertexai._utils import ( create_retry_decorator, get_client_info, + get_generation_info, is_codey_model, is_gemini_model, ) @@ -66,7 +71,10 @@ def _completion_with_retry_inner( ) -> Any: if is_gemini: return llm.client.generate_content( - prompt, stream=stream, generation_config=kwargs + prompt, + stream=stream, + safety_settings=kwargs.pop("safety_settings", None), + generation_config=kwargs, ) else: if stream: @@ -94,7 +102,9 @@ async def _acompletion_with_retry_inner( ) -> Any: if is_gemini: return await llm.client.generate_content_async( - prompt, generation_config=kwargs + prompt, + generation_config=kwargs, + safety_settings=kwargs.pop("safety_settings", None), ) return await llm.client.predict_async(prompt, **kwargs) @@ -141,6 +151,21 @@ class _VertexAICommon(_VertexAIBase): """How many completions to generate for each prompt.""" streaming: bool = False """Whether to stream the results or not.""" + safety_settings: Optional[Dict[HarmCategory, HarmBlockThreshold]] = None + """The default safety settings to use for all generations. + + For example: + + from langchain_google_vertexai import HarmBlockThreshold, HarmCategory + + safety_settings = { + HarmCategory.HARM_CATEGORY_UNSPECIFIED: HarmBlockThreshold.BLOCK_NONE, + HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, + HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH, + HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, + HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE, + } + """ # noqa: E501 @property def _llm_type(self) -> str: @@ -237,9 +262,13 @@ def validate_environment(cls, values: Dict) -> Dict: """Validate that the python package exists in environment.""" tuned_model_name = values.get("tuned_model_name") model_name = values["model_name"] + safety_settings = values["safety_settings"] is_gemini = is_gemini_model(values["model_name"]) cls._init_vertexai(values) + if safety_settings and (not is_gemini or tuned_model_name): + raise ValueError("Safety settings are only supported for Gemini models") + if is_codey_model(model_name): model_cls = CodeGenerationModel preview_model_cls = PreviewCodeGenerationModel @@ -257,8 +286,12 @@ def validate_environment(cls, values: Dict) -> Dict: ) else: if is_gemini: - values["client"] = model_cls(model_name=model_name) - values["client_preview"] = preview_model_cls(model_name=model_name) + values["client"] = model_cls( + model_name=model_name, safety_settings=safety_settings + ) + values["client_preview"] = preview_model_cls( + model_name=model_name, safety_settings=safety_settings + ) else: values["client"] = model_cls.from_pretrained(model_name) values["client_preview"] = preview_model_cls.from_pretrained(model_name) @@ -285,14 +318,14 @@ def _response_to_generation( self, response: TextGenerationResponse ) -> GenerationChunk: """Converts a stream response to a generation chunk.""" - try: - generation_info = { - "is_blocked": response.is_blocked, - "safety_attributes": response.safety_attributes, - } - except Exception: - generation_info = None - return GenerationChunk(text=response.text, generation_info=generation_info) + generation_info = get_generation_info(response, self._is_gemini_model) + + return GenerationChunk( + text=response.text + if hasattr(response, "text") + else "", # might not exist if blocked + generation_info=generation_info, + ) def _generate( self, diff --git a/libs/partners/google-vertexai/poetry.lock b/libs/partners/google-vertexai/poetry.lock index 4c55611ca7920..48233dce17e2c 100644 --- a/libs/partners/google-vertexai/poetry.lock +++ b/libs/partners/google-vertexai/poetry.lock @@ -504,13 +504,13 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.26.1" +version = "2.26.2" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.26.1.tar.gz", hash = "sha256:54385acca5c0fbdda510cd8585ba6f3fcb06eeecf8a6ecca39d3ee148b092590"}, - {file = "google_auth-2.26.1-py2.py3-none-any.whl", hash = "sha256:2c8b55e3e564f298122a02ab7b97458ccfcc5617840beb5d0ac757ada92c9780"}, + {file = "google-auth-2.26.2.tar.gz", hash = "sha256:97327dbbf58cccb58fc5a1712bba403ae76668e64814eb30f7316f7e27126b81"}, + {file = "google_auth-2.26.2-py2.py3-none-any.whl", hash = "sha256:3f445c8ce9b61ed6459aad86d8ccdba4a9afed841b2d1451a11ef4db08957424"}, ] [package.dependencies] @@ -582,13 +582,13 @@ xai = ["tensorflow (>=2.3.0,<3.0.0dev)"] [[package]] name = "google-cloud-bigquery" -version = "3.14.1" +version = "3.16.0" description = "Google BigQuery API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-bigquery-3.14.1.tar.gz", hash = "sha256:aa15bd86f79ea76824c7d710f5ae532323c4b3ba01ef4abff42d4ee7a2e9b142"}, - {file = "google_cloud_bigquery-3.14.1-py2.py3-none-any.whl", hash = "sha256:a8ded18455da71508db222b7c06197bc12b6dbc6ed5b0b64e7007b76d7016957"}, + {file = "google-cloud-bigquery-3.16.0.tar.gz", hash = "sha256:1d6abf4b1d740df17cb43a078789872af8059a0b1dd999f32ea69ebc6f7ba7ef"}, + {file = "google_cloud_bigquery-3.16.0-py2.py3-none-any.whl", hash = "sha256:8bac7754f92bf87ee81f38deabb7554d82bb9591fbe06a5c82f33e46e5a482f9"}, ] [package.dependencies] @@ -1110,13 +1110,13 @@ url = "../../core" [[package]] name = "langsmith" -version = "0.0.77" +version = "0.0.81" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.77-py3-none-any.whl", hash = "sha256:750c0aa9177240c64e131d831e009ed08dd59038f7cabbd0bbcf62ccb7c8dcac"}, - {file = "langsmith-0.0.77.tar.gz", hash = "sha256:c4c8d3a96ad8671a41064f3ccc673e2e22a4153e823b19f915c9c9b8a4f33a2c"}, + {file = "langsmith-0.0.81-py3-none-any.whl", hash = "sha256:eb816ad456776ec4c6005ddce8a4c315a1a582ed4d079979888e9f8a1db209b3"}, + {file = "langsmith-0.0.81.tar.gz", hash = "sha256:5838e5a4bb1939e9794eb3f802f7c390247a847bd603e31442be5be00068e504"}, ] [package.dependencies] @@ -1410,22 +1410,22 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] [[package]] name = "protobuf" -version = "4.25.1" +version = "4.25.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, - {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, - {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, - {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, - {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, - {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, - {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, - {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, - {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, + {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, + {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, + {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, + {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, + {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, + {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, + {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, + {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, + {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, ] [[package]] @@ -1775,28 +1775,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.1.11" +version = "0.1.13" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"}, - {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"}, - {file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"}, - {file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"}, - {file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"}, - {file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"}, + {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"}, + {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"}, + {file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"}, + {file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"}, + {file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"}, + {file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"}, ] [[package]] @@ -2033,24 +2033,24 @@ files = [ [[package]] name = "types-protobuf" -version = "4.24.0.4" +version = "4.24.0.20240106" description = "Typing stubs for protobuf" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "types-protobuf-4.24.0.4.tar.gz", hash = "sha256:57ab42cb171dfdba2c74bb5b50c250478538cc3c5ed95b8b368929ad0c9f90a5"}, - {file = "types_protobuf-4.24.0.4-py3-none-any.whl", hash = "sha256:131ab7d0cbc9e444bc89c994141327dcce7bcaeded72b1acb72a94827eb9c7af"}, + {file = "types-protobuf-4.24.0.20240106.tar.gz", hash = "sha256:024f034f3b5e2bb2bbff55ebc4d591ed0d2280d90faceedcb148b9e714a3f3ee"}, + {file = "types_protobuf-4.24.0.20240106-py3-none-any.whl", hash = "sha256:0612ef3156bd80567460a15ac7c109b313f6022f1fee04b4d922ab2789baab79"}, ] [[package]] name = "types-requests" -version = "2.31.0.20231231" +version = "2.31.0.20240106" description = "Typing stubs for requests" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20231231.tar.gz", hash = "sha256:0f8c0c9764773384122813548d9eea92a5c4e1f33ed54556b508968ec5065cee"}, - {file = "types_requests-2.31.0.20231231-py3-none-any.whl", hash = "sha256:2e2230c7bc8dd63fa3153c1c0ae335f8a368447f0582fc332f17d54f88e69027"}, + {file = "types-requests-2.31.0.20240106.tar.gz", hash = "sha256:0e1c731c17f33618ec58e022b614a1a2ecc25f7dc86800b36ef341380402c612"}, + {file = "types_requests-2.31.0.20240106-py3-none-any.whl", hash = "sha256:da997b3b6a72cc08d09f4dba9802fdbabc89104b35fe24ee588e674037689354"}, ] [package.dependencies] diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py b/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py index 5bf65d99b7dc2..2b280515635e5 100644 --- a/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py +++ b/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py @@ -1,4 +1,6 @@ """Test ChatGoogleVertexAI chat model.""" +from typing import cast + import pytest from langchain_core.messages import ( AIMessage, @@ -6,7 +8,7 @@ HumanMessage, SystemMessage, ) -from langchain_core.outputs import LLMResult +from langchain_core.outputs import ChatGeneration, LLMResult from langchain_google_vertexai.chat_models import ChatVertexAI @@ -60,7 +62,13 @@ async def test_vertexai_agenerate(model_name: str) -> None: assert isinstance(response.generations[0][0].message, AIMessage) # type: ignore sync_response = model.generate([[message]]) - assert response.generations[0][0] == sync_response.generations[0][0] + sync_generation = cast(ChatGeneration, sync_response.generations[0][0]) + async_generation = cast(ChatGeneration, response.generations[0][0]) + + # assert some properties to make debugging easier + assert sync_generation.message.content == async_generation.message.content + assert sync_generation.generation_info == async_generation.generation_info + assert sync_generation == async_generation @pytest.mark.parametrize("model_name", ["chat-bison@001", "gemini-pro"]) diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_llms.py b/libs/partners/google-vertexai/tests/integration_tests/test_llms.py index 14f84c0616223..823c8671dc9e7 100644 --- a/libs/partners/google-vertexai/tests/integration_tests/test_llms.py +++ b/libs/partners/google-vertexai/tests/integration_tests/test_llms.py @@ -42,6 +42,7 @@ def test_vertex_call(model_name: str) -> None: assert isinstance(output, str) +@pytest.mark.xfail(reason="VertexAI doesn't always respect number of candidates") def test_vertex_generate() -> None: llm = VertexAI(temperature=0.3, n=2, model_name="text-bison@001") output = llm.generate(["Say foo:"]) @@ -50,6 +51,7 @@ def test_vertex_generate() -> None: assert len(output.generations[0]) == 2 +@pytest.mark.xfail(reason="VertexAI doesn't always respect number of candidates") def test_vertex_generate_code() -> None: llm = VertexAI(temperature=0.3, n=2, model_name="code-bison@001") output = llm.generate(["generate a python method that says foo:"]) @@ -87,6 +89,7 @@ async def test_vertex_consistency() -> None: assert output.generations[0][0].text == async_output.generations[0][0].text +@pytest.mark.skip("CI testing not set up") @pytest.mark.parametrize( "endpoint_os_variable_name,result_arg", [("FALCON_ENDPOINT_ID", "generated_text"), ("LLAMA_ENDPOINT_ID", None)], @@ -115,6 +118,7 @@ def test_model_garden( assert llm._llm_type == "vertexai_model_garden" +@pytest.mark.skip("CI testing not set up") @pytest.mark.parametrize( "endpoint_os_variable_name,result_arg", [("FALCON_ENDPOINT_ID", "generated_text"), ("LLAMA_ENDPOINT_ID", None)], @@ -143,6 +147,7 @@ def test_model_garden_generate( assert len(output.generations) == 2 +@pytest.mark.skip("CI testing not set up") @pytest.mark.asyncio @pytest.mark.parametrize( "endpoint_os_variable_name,result_arg", diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_llms_safety.py b/libs/partners/google-vertexai/tests/integration_tests/test_llms_safety.py new file mode 100644 index 0000000000000..7e526cbf27ad5 --- /dev/null +++ b/libs/partners/google-vertexai/tests/integration_tests/test_llms_safety.py @@ -0,0 +1,97 @@ +from langchain_core.outputs import LLMResult + +from langchain_google_vertexai import HarmBlockThreshold, HarmCategory, VertexAI + +SAFETY_SETTINGS = { + HarmCategory.HARM_CATEGORY_UNSPECIFIED: HarmBlockThreshold.BLOCK_NONE, + HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE, + HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE, + HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE, + HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE, +} + + +# below context and question are taken from one of opensource QA datasets +BLOCKED_PROMPT = """ +You are agent designed to answer questions. +You are given context in triple backticks. +``` +The religion\'s failure to report abuse allegations to authorities has also been +criticized. The Watch Tower Society\'s policy is that elders inform authorities when + required by law to do so, but otherwise leave that action up to the victim and his + or her family. The Australian Royal Commission into Institutional Responses to Child +Sexual Abuse found that of 1006 alleged perpetrators of child sexual abuse +identified by the Jehovah\'s Witnesses within their organization since 1950, +"not one was reported by the church to secular authorities." William Bowen, a former +Jehovah\'s Witness elder who established the Silentlambs organization to assist sex +abuse victims within the religion, has claimed Witness leaders discourage followers +from reporting incidents of sexual misconduct to authorities, and other critics claim +the organization is reluctant to alert authorities in order to protect its "crime-free" + reputation. In court cases in the United Kingdom and the United States the Watch Tower + Society has been found to have been negligent in its failure to protect children from + known sex offenders within the congregation and the Society has settled other child +abuse lawsuits out of court, reportedly paying as much as $780,000 to one plaintiff +without admitting wrongdoing. +``` +Question: What have courts in both the UK and the US found the Watch Tower Society to + have been for failing to protect children from sexual predators within the + congregation ? +Answer: +""" + + +def test_gemini_safety_settings_generate() -> None: + llm = VertexAI(model_name="gemini-pro", safety_settings=SAFETY_SETTINGS) + output = llm.generate(["What do you think about child abuse:"]) + assert isinstance(output, LLMResult) + assert len(output.generations) == 1 + generation_info = output.generations[0][0].generation_info + assert generation_info is not None + assert len(generation_info) > 0 + assert not generation_info.get("is_blocked") + + blocked_output = llm.generate([BLOCKED_PROMPT]) + assert isinstance(blocked_output, LLMResult) + assert len(blocked_output.generations) == 1 + assert len(blocked_output.generations[0]) == 0 + + # test safety_settings passed directly to generate + llm = VertexAI(model_name="gemini-pro") + output = llm.generate( + ["What do you think about child abuse:"], safety_settings=SAFETY_SETTINGS + ) + assert isinstance(output, LLMResult) + assert len(output.generations) == 1 + generation_info = output.generations[0][0].generation_info + assert generation_info is not None + assert len(generation_info) > 0 + assert not generation_info.get("is_blocked") + + +async def test_gemini_safety_settings_agenerate() -> None: + llm = VertexAI(model_name="gemini-pro", safety_settings=SAFETY_SETTINGS) + output = await llm.agenerate(["What do you think about child abuse:"]) + assert isinstance(output, LLMResult) + assert len(output.generations) == 1 + generation_info = output.generations[0][0].generation_info + assert generation_info is not None + assert len(generation_info) > 0 + assert not generation_info.get("is_blocked") + + blocked_output = await llm.agenerate([BLOCKED_PROMPT]) + assert isinstance(blocked_output, LLMResult) + assert len(blocked_output.generations) == 1 + # assert len(blocked_output.generations[0][0].generation_info) > 0 + # assert blocked_output.generations[0][0].generation_info.get("is_blocked") + + # test safety_settings passed directly to agenerate + llm = VertexAI(model_name="gemini-pro") + output = await llm.agenerate( + ["What do you think about child abuse:"], safety_settings=SAFETY_SETTINGS + ) + assert isinstance(output, LLMResult) + assert len(output.generations) == 1 + generation_info = output.generations[0][0].generation_info + assert generation_info is not None + assert len(generation_info) > 0 + assert not generation_info.get("is_blocked") diff --git a/libs/partners/google-vertexai/tests/unit_tests/test_imports.py b/libs/partners/google-vertexai/tests/unit_tests/test_imports.py index 016d6e21c73ba..11e91afcbe0c4 100644 --- a/libs/partners/google-vertexai/tests/unit_tests/test_imports.py +++ b/libs/partners/google-vertexai/tests/unit_tests/test_imports.py @@ -1,6 +1,13 @@ from langchain_google_vertexai import __all__ -EXPECTED_ALL = ["ChatVertexAI", "VertexAIEmbeddings", "VertexAI", "VertexAIModelGarden"] +EXPECTED_ALL = [ + "ChatVertexAI", + "VertexAIEmbeddings", + "VertexAI", + "VertexAIModelGarden", + "HarmBlockThreshold", + "HarmCategory", +] def test_all_imports() -> None: From aa2e642ce3bc073676000404afb3695c31a3db9f Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Thu, 18 Jan 2024 09:17:53 -0800 Subject: [PATCH 086/215] docs: tool use nits (#16211) --- docs/docs/use_cases/tool_use/index.ipynb | 2 +- docs/docs/use_cases/tool_use/prompting.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/use_cases/tool_use/index.ipynb b/docs/docs/use_cases/tool_use/index.ipynb index 5f85d631bc004..b31e5bb397b66 100644 --- a/docs/docs/use_cases/tool_use/index.ipynb +++ b/docs/docs/use_cases/tool_use/index.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "source": [ "---\n", - "sidebar_position: 2\n", + "sidebar_position: 0.9\n", "---" ] }, diff --git a/docs/docs/use_cases/tool_use/prompting.ipynb b/docs/docs/use_cases/tool_use/prompting.ipynb index 2b30bf2ec6491..6cd877fe2e468 100644 --- a/docs/docs/use_cases/tool_use/prompting.ipynb +++ b/docs/docs/use_cases/tool_use/prompting.ipynb @@ -15,7 +15,7 @@ "id": "14b94240", "metadata": {}, "source": [ - "# Prompting for tool use\n", + "# Tool use without function calling\n", "\n", "In this guide we'll build a Chain that does not rely on any special model APIs (like function-calling, which we showed in the [Quickstart](/docs/use_cases/tool_use/quickstart)) and instead just prompts the model directly to invoke tools." ] From ed118950fe62bf28b0905e8acfbb026636767698 Mon Sep 17 00:00:00 2001 From: jzaldi <jzaldi89@gmail.com> Date: Thu, 18 Jan 2024 18:45:27 +0100 Subject: [PATCH 087/215] docs: Updated integration docs structure for llm/google_vertex_ai_palm (#16091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Description**: Updated doc for llm/google_vertex_ai_palm with new functions: `invoke`, `stream`... Changed structure of the document to match the required one. - **Issue**: #15664 - **Dependencies**: None - **Twitter handle**: None --------- Co-authored-by: Jorge Zaldívar <jzaldivar@google.com> --- .../llms/google_vertex_ai_palm.ipynb | 354 +++++++----------- 1 file changed, 142 insertions(+), 212 deletions(-) diff --git a/docs/docs/integrations/llms/google_vertex_ai_palm.ipynb b/docs/docs/integrations/llms/google_vertex_ai_palm.ipynb index 9a4edee338fdd..4f53a75c925b4 100644 --- a/docs/docs/integrations/llms/google_vertex_ai_palm.ipynb +++ b/docs/docs/integrations/llms/google_vertex_ai_palm.ipynb @@ -11,29 +11,30 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "xazoWTniN8Xa" - }, + "metadata": {}, "source": [ "# Google Cloud Vertex AI\n", "\n", - "**Note:** This is separate from the `Google Generative AI` integration, it exposes [Vertex AI Generative API](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/overview) on `Google Cloud`.\n" + "**Note:** This is separate from the `Google Generative AI` integration, it exposes [Vertex AI Generative API](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/overview) on `Google Cloud`.\n", + "\n", + "VertexAI exposes all foundational models available in google cloud:\n", + "- Gemini (`gemini-pro` and `gemini-pro-vision`)\n", + "- Palm 2 for Text (`text-bison`)\n", + "- Codey for Code Generation (`code-bison`)\n", + "\n", + "For a full and updated list of available models visit [VertexAI documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/overview)" ] }, { "cell_type": "markdown", - "metadata": { - "id": "Q_UoF2FKN8Xb" - }, + "metadata": {}, "source": [ - "## Setting up" + "## Setup" ] }, { "cell_type": "markdown", - "metadata": { - "id": "8uImJzc4N8Xb" - }, + "metadata": {}, "source": [ "By default, Google Cloud [does not use](https://cloud.google.com/vertex-ai/docs/generative-ai/data-governance#foundation_model_development) customer data to train its foundation models as part of Google Cloud's AI/ML Privacy Commitment. More details about how Google processes data can also be found in [Google's Customer Data Processing Addendum (CDPA)](https://cloud.google.com/terms/data-processing-addendum).\n", "\n", @@ -52,78 +53,29 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], - "source": [ - "%pip install --upgrade --quiet langchain-core langchain-google-vertexai" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " **Pros of Python:**\n", "\n", - "* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners. It also has a large and supportive community, with many resources available online.\n", - "* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and artificial intelligence.\n", - "* **Powerful:** Python has a rich library of built-in functions and modules, making it easy to perform complex tasks without having to write a lot of code.\n", - "* **Cross-platform:** Python can be run on a variety of operating systems\n" + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ - "from langchain_google_vertexai import VertexAI\n", - "\n", - "llm = VertexAI()\n", - "print(llm(\"What are some of the pros and cons of Python as a programming language?\"))" + "%pip install --upgrade --quiet langchain-core langchain-google-vertexai" ] }, { "cell_type": "markdown", - "metadata": { - "id": "38S1FS3qN8Xc" - }, - "source": [ - "You can also use Gemini model (in preview) with VertexAI:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "**Pros of Python:**\n", - "\n", - "* **Easy to learn and use:** Python is known for its simplicity and readability, making it a great choice for beginners and experienced programmers alike. Its syntax is straightforward and intuitive, allowing developers to quickly pick up the language and start writing code.\n", - "\n", - "\n", - "* **Versatile:** Python is a general-purpose language that can be used for a wide range of applications, including web development, data science, machine learning, and scripting. Its extensive standard library and vast ecosystem of third-party modules make it suitable for a variety of tasks.\n", - "\n", - "\n", - "* **Cross-platform:** Python is compatible with multiple operating systems, including\n" - ] - } - ], "source": [ - "llm = VertexAI(model_name=\"gemini-pro\")\n", - "print(llm(\"What are some of the pros and cons of Python as a programming language?\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_-9MhhN8N8Xc" - }, - "source": [ - "## Using in a chain" + "## Usage\n", + "\n", + "VertexAI supports all [LLM](/docs/modules/model_io/llms/) functionality." ] }, { @@ -132,12 +84,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain_core.prompts import PromptTemplate\n", - "\n", - "template = \"\"\"Question: {question}\n", - "\n", - "Answer: Let's think step by step.\"\"\"\n", - "prompt = PromptTemplate.from_template(template)" + "from langchain_google_vertexai import VertexAI" ] }, { @@ -146,7 +93,7 @@ "metadata": {}, "outputs": [], "source": [ - "chain = prompt | llm" + "model = VertexAI(model_name=\"gemini-pro\")" ] }, { @@ -155,57 +102,39 @@ "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - " Justin Bieber was born on March 1, 1994. Bill Clinton was the president of the United States from January 20, 1993, to January 20, 2001.\n", - "The final answer is Bill Clinton\n" - ] + "data": { + "text/plain": [ + "'**Pros:**\\n\\n* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners and experienced programmers alike.\\n* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and scripting.\\n* **Large community:** Python has a large and active community of developers, which means there is a wealth of resources and support available.\\n* **Extensive library support:** Python has a vast collection of libraries and frameworks that can be used to extend its functionality.\\n* **Cross-platform:** Python is available for a'" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "question = \"Who was the president in the year Justin Beiber was born?\"\n", - "print(chain.invoke({\"question\": question}))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "AV7oXXuHN8Xd" - }, - "source": [ - "## Code generation example" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3ZzVtF6tN8Xd" - }, - "source": [ - "You can now leverage the `Codey API` for code generation within `Vertex AI`.\n", - "\n", - "The model names are:\n", - "- `code-bison`: for code suggestion\n", - "- `code-gecko`: for code completion" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "llm = VertexAI(model_name=\"code-bison\", max_output_tokens=1000, temperature=0.3)" + "message = \"What are some of the pros and cons of Python as a programming language?\"\n", + "model.invoke(message)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'**Pros:**\\n\\n* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners and experienced programmers alike.\\n* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and scripting.\\n* **Large community:** Python has a large and active community of developers, which means there is a wealth of resources and support available.\\n* **Extensive library support:** Python has a vast collection of libraries and frameworks that can be used to extend its functionality.\\n* **Cross-platform:** Python is available for a'" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "question = \"Write a python function that checks if a string is a valid email address\"" + "await model.ainvoke(message)" ] }, { @@ -217,29 +146,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "```python\n", - "import re\n", + "**Pros:**\n", "\n", - "def is_valid_email(email):\n", - " pattern = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n", - " return pattern.match(email)\n", - "```\n" + "* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners and experienced programmers alike.\n", + "* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and scripting.\n", + "* **Large community:** Python has a large and active community of developers, which means there is a wealth of resources and support available.\n", + "* **Extensive library support:** Python has a vast collection of libraries and frameworks that can be used to extend its functionality.\n", + "* **Cross-platform:** Python is available for a" ] } ], "source": [ - "print(llm(question))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "0WqyaSC2N8Xd" - }, - "source": [ - "## Full generation info\n", - "\n", - "We can use the `generate` method to get back extra metadata like [safety attributes](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/responsible-ai#safety_attribute_confidence_scoring) and not just text completions" + "for chunk in model.stream(message):\n", + " print(chunk, end=\"\", flush=True)" ] }, { @@ -250,43 +169,44 @@ { "data": { "text/plain": [ - "[[GenerationChunk(text='```python\\nimport re\\n\\ndef is_valid_email(email):\\n pattern = re.compile(r\"[^@]+@[^@]+\\\\.[^@]+\")\\n return pattern.match(email)\\n```', generation_info={'is_blocked': False, 'safety_attributes': {'Health': 0.1}})]]" + "['**Pros:**\\n\\n* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners and experienced programmers alike.\\n* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and scripting.\\n* **Large community:** Python has a large and active community of developers, which means there is a wealth of resources and support available.\\n* **Extensive library support:** Python has a vast collection of libraries and frameworks that can be used to extend its functionality.\\n* **Cross-platform:** Python is available for a']" ] }, - "execution_count": 23, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "result = llm.generate([question])\n", - "result.generations" + "model.batch([message])" ] }, { "cell_type": "markdown", - "metadata": { - "id": "Wd5M4BBUN8Xd" - }, + "metadata": {}, "source": [ - "## Asynchronous calls\n", - "\n", - "With `agenerate` we can make asynchronous calls" + "We can use the `generate` method to get back extra metadata like [safety attributes](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/responsible-ai#safety_attribute_confidence_scoring) and not just text completions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[[GenerationChunk(text='**Pros:**\\n\\n* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners and experienced programmers alike.\\n* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and scripting.\\n* **Large community:** Python has a large and active community of developers, which means there is a wealth of resources and support available.\\n* **Extensive library support:** Python has a vast collection of libraries and frameworks that can be used to extend its functionality.\\n* **Cross-platform:** Python is available for a')]]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# If running in a Jupyter notebook you'll need to install nest_asyncio\n", - "\n", - "%pip install --upgrade --quiet nest_asyncio\n", - "\n", - "import nest_asyncio\n", - "\n", - "nest_asyncio.apply()" + "result = model.generate([message])\n", + "result.generations" ] }, { @@ -297,38 +217,65 @@ { "data": { "text/plain": [ - "LLMResult(generations=[[GenerationChunk(text='```python\\nimport re\\n\\ndef is_valid_email(email):\\n pattern = re.compile(r\"[^@]+@[^@]+\\\\.[^@]+\")\\n return pattern.match(email)\\n```', generation_info={'is_blocked': False, 'safety_attributes': {'Health': 0.1}})]], llm_output=None, run=[RunInfo(run_id=UUID('caf74e91-aefb-48ac-8031-0c505fcbbcc6'))])" + "[[GenerationChunk(text='**Pros:**\\n\\n* **Easy to learn and use:** Python is known for its simple syntax and readability, making it a great choice for beginners and experienced programmers alike.\\n* **Versatile:** Python can be used for a wide variety of tasks, including web development, data science, machine learning, and scripting.\\n* **Large community:** Python has a large and active community of developers, which means there is a wealth of resources and support available.\\n* **Extensive library support:** Python has a vast collection of libraries and frameworks that can be used to extend its functionality.\\n* **Cross-platform:** Python is available for a')]]" ] }, - "execution_count": 25, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "import asyncio\n", - "\n", - "asyncio.run(llm.agenerate([question]))" + "result = await model.agenerate([message])\n", + "result.generations" ] }, { "cell_type": "markdown", - "metadata": { - "id": "VLsy_4bZN8Xd" - }, + "metadata": {}, "source": [ - "## Streaming calls\n", - "\n", - "With `stream` we can stream results from the model" + "You can also easily combine with a prompt template for easy structuring of user input. We can do this using [LCEL](/docs/expression_language)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1. You start with 5 apples.\n", + "2. You throw away 2 apples, so you have 5 - 2 = 3 apples left.\n", + "3. You eat 1 apple, so you have 3 - 1 = 2 apples left.\n", + "\n", + "Therefore, you have 2 apples left.\n" + ] + } + ], + "source": [ + "from langchain_core.prompts import PromptTemplate\n", + "\n", + "template = \"\"\"Question: {question}\n", + "\n", + "Answer: Let's think step by step.\"\"\"\n", + "prompt = PromptTemplate.from_template(template)\n", + "\n", + "chain = prompt | model\n", + "\n", + "question = \"\"\"\n", + "I have five apples. I throw two away. I eat one. How many apples do I have left?\n", + "\"\"\"\n", + "print(chain.invoke({\"question\": question}))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ - "import sys" + "You can use different foundational models for specialized in different tasks. \n", + "For an updated list of available models visit [VertexAI documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/overview)" ] }, { @@ -354,49 +301,38 @@ " True if the string is a valid email address, False otherwise.\n", " \"\"\"\n", "\n", - " # Check for a valid email address format.\n", - " if not re.match(r\"^[A-Za-z0-9\\.\\+_-]+@[A-Za-z0-9\\._-]+\\.[a-zA-Z]*$\", email):\n", - " return False\n", - "\n", - " # Check if the domain name exists.\n", - " try:\n", - " domain = email.split(\"@\")[1]\n", - " socket.gethostbyname(domain)\n", - " except socket.gaierror:\n", - " return False\n", + " # Compile the regular expression for an email address.\n", + " regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n", "\n", - " return True\n", - "```" + " # Check if the string matches the regular expression.\n", + " return regex.match(email) is not None\n", + "```\n" ] } ], "source": [ - "for chunk in llm.stream(question):\n", - " sys.stdout.write(chunk)\n", - " sys.stdout.flush()" + "llm = VertexAI(model_name=\"code-bison\", max_output_tokens=1000, temperature=0.3)\n", + "question = \"Write a python function that checks if a string is a valid email address\"\n", + "print(model.invoke(question))" ] }, { "cell_type": "markdown", - "metadata": { - "id": "4VJ8GwhaN8Xd" - }, + "metadata": {}, "source": [ "## Multimodality" ] }, { "cell_type": "markdown", - "metadata": { - "id": "L7BovARaN8Xe" - }, + "metadata": {}, "source": [ "With Gemini, you can use LLM in a multimodal mode:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -429,16 +365,14 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "3Vk3gQrrOaL9" - }, + "metadata": {}, "source": [ "Let's double-check it's a cat :)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -448,7 +382,7 @@ "<vertexai.generative_models._generative_models.Image at 0x791ded5f1ed0>" ] }, - "execution_count": 9, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -462,16 +396,14 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "1uEACSSm8AL2" - }, + "metadata": {}, "source": [ "You can also pass images as bytes:" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -506,18 +438,14 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "AuhF5WQuN8Xe" - }, + "metadata": {}, "source": [ "Please, note that you can also use the image stored in GCS (just point the `url` to the full GCS path, starting with `gs://` instead of a local one)." ] }, { "cell_type": "markdown", - "metadata": { - "id": "qaC2UmxS9WtB" - }, + "metadata": {}, "source": [ "And you can also pass a history of a previous chat to the LLM:" ] @@ -564,18 +492,14 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "VEYAfdBpN8Xe" - }, + "metadata": {}, "source": [ "## Vertex Model Garden" ] }, { "cell_type": "markdown", - "metadata": { - "id": "N3ptjr_LN8Xe" - }, + "metadata": {}, "source": [ "Vertex Model Garden [exposes](https://cloud.google.com/vertex-ai/docs/start/explore-models) open-sourced models that can be deployed and served on Vertex AI. If you have successfully deployed a model from Vertex Model Garden, you can find a corresponding Vertex AI [endpoint](https://cloud.google.com/vertex-ai/docs/general/deployment#what_happens_when_you_deploy_a_model) in the console or via API." ] @@ -604,14 +528,12 @@ "metadata": {}, "outputs": [], "source": [ - "print(llm(\"What is the meaning of life?\"))" + "llm.invoke(\"What is the meaning of life?\")" ] }, { "cell_type": "markdown", - "metadata": { - "id": "TDXoFZ6YN8Xe" - }, + "metadata": {}, "source": [ "Like all LLMs, we can then compose it with other components:" ] @@ -643,8 +565,16 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "version": "3.11.4" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" } }, "nbformat": 4, From 65b231d40bfd687f8ec305fa806246f72b82e389 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 09:45:44 -0800 Subject: [PATCH 088/215] mistralai[patch]: async integration tests (#16214) --- .../integration_tests/test_embeddings.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/libs/partners/mistralai/tests/integration_tests/test_embeddings.py b/libs/partners/mistralai/tests/integration_tests/test_embeddings.py index b179f64d80f14..166bf5c5c15be 100644 --- a/libs/partners/mistralai/tests/integration_tests/test_embeddings.py +++ b/libs/partners/mistralai/tests/integration_tests/test_embeddings.py @@ -17,3 +17,37 @@ def test_mistralai_embedding_query() -> None: embedding = MistralAIEmbeddings() output = embedding.embed_query(document) assert len(output) == 1024 + + +async def test_mistralai_embedding_documents_async() -> None: + """Test MistralAI embeddings for documents.""" + documents = ["foo bar", "test document"] + embedding = MistralAIEmbeddings() + output = await embedding.aembed_documents(documents) + assert len(output) == 2 + assert len(output[0]) == 1024 + + +async def test_mistralai_embedding_query_async() -> None: + """Test MistralAI embeddings for query.""" + document = "foo bar" + embedding = MistralAIEmbeddings() + output = await embedding.aembed_query(document) + assert len(output) == 1024 + + +def test_mistralai_embedding_documents_long() -> None: + """Test MistralAI embeddings for documents.""" + documents = ["foo bar " * 1000, "test document " * 1000] + embedding = MistralAIEmbeddings() + output = embedding.embed_documents(documents) + assert len(output) == 2 + assert len(output[0]) == 1024 + + +def test_mistralai_embed_query_character() -> None: + """Test MistralAI embeddings for query.""" + document = "😳" + embedding = MistralAIEmbeddings() + output = embedding.embed_query(document) + assert len(output) == 1024 From 6bc6d64a12b7794ce72f2b0f853e4c0cbed55685 Mon Sep 17 00:00:00 2001 From: Rajesh Thallam <rajesh.thallam@ischool.berkeley.edu> Date: Thu, 18 Jan 2024 10:22:07 -0800 Subject: [PATCH 089/215] langchain_google_vertexai[patch]: Add support for SystemMessage for Gemini chat model (#15933) - **Description:** In Google Vertex AI, Gemini Chat models currently doesn't have a support for SystemMessage. This PR adds support for it only if a user provides additional convert_system_message_to_human flag during model initialization (in this case, SystemMessage would be prepended to the first HumanMessage). **NOTE:** The implementation is similar to #14824 - **Twitter handle:** rajesh_thallam --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- .../chat/google_vertex_ai_palm.ipynb | 54 ++++++++++++++--- .../langchain_google_vertexai/chat_models.py | 58 +++++++++++++++++-- .../integration_tests/test_chat_models.py | 33 +++++++++++ .../tests/unit_tests/test_chat_models.py | 19 ++++++ 4 files changed, 151 insertions(+), 13 deletions(-) diff --git a/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb b/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb index 0443dbf844285..050a32f2bf63c 100644 --- a/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb +++ b/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb @@ -11,7 +11,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -95,7 +94,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If we want to construct a simple chain that takes user specified parameters:" + "Gemini doesn't support SystemMessage at the moment, but it can be added to the first human message in the row. If you want such behavior, just set the `convert_system_message_to_human` to `True`:" ] }, { @@ -106,7 +105,7 @@ { "data": { "text/plain": [ - "AIMessage(content=' プログラミングが大好きです')" + "AIMessage(content=\"J'aime la programmation.\")" ] }, "execution_count": 9, @@ -114,6 +113,40 @@ "output_type": "execute_result" } ], + "source": [ + "system = \"You are a helpful assistant who translate English to French\"\n", + "human = \"Translate this sentence from English to French. I love programming.\"\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n", + "\n", + "chat = ChatVertexAI(model_name=\"gemini-pro\", convert_system_message_to_human=True)\n", + "\n", + "chain = prompt | chat\n", + "chain.invoke({})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we want to construct a simple chain that takes user specified parameters:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=' プログラミングが大好きです')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "system = (\n", " \"You are a helpful assistant that translates {input_language} to {output_language}.\"\n", @@ -121,6 +154,8 @@ "human = \"{text}\"\n", "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n", "\n", + "chat = ChatVertexAI()\n", + "\n", "chain = prompt | chat\n", "\n", "chain.invoke(\n", @@ -133,7 +168,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": { "execution": { @@ -352,7 +386,7 @@ "AIMessage(content=' Why do you love programming?')" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -428,8 +462,14 @@ } ], "metadata": { + "environment": { + "kernel": "python3", + "name": "common-cpu.m108", + "type": "gcloud", + "uri": "gcr.io/deeplearning-platform-release/base-cpu:m108" + }, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -443,7 +483,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.10.10" }, "vscode": { "interpreter": { diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py b/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py index a76ade2d227d9..1c62d76c75c0d 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/chat_models.py @@ -111,7 +111,9 @@ def _is_url(s: str) -> bool: def _parse_chat_history_gemini( - history: List[BaseMessage], project: Optional[str] + history: List[BaseMessage], + project: Optional[str] = None, + convert_system_message_to_human: Optional[bool] = False, ) -> List[Content]: def _convert_to_prompt(part: Union[str, Dict]) -> Part: if isinstance(part, str): @@ -155,9 +157,25 @@ def _convert_to_parts(message: BaseMessage) -> List[Part]: return [_convert_to_prompt(part) for part in raw_content] vertex_messages = [] + raw_system_message = None for i, message in enumerate(history): - if i == 0 and isinstance(message, SystemMessage): - raise ValueError("SystemMessages are not yet supported!") + if ( + i == 0 + and isinstance(message, SystemMessage) + and not convert_system_message_to_human + ): + raise ValueError( + """SystemMessages are not yet supported! + +To automatically convert the leading SystemMessage to a HumanMessage, +set `convert_system_message_to_human` to True. Example: + +llm = ChatVertexAI(model_name="gemini-pro", convert_system_message_to_human=True) +""" + ) + elif i == 0 and isinstance(message, SystemMessage): + raw_system_message = message + continue elif isinstance(message, AIMessage): raw_function_call = message.additional_kwargs.get("function_call") role = "model" @@ -170,6 +188,8 @@ def _convert_to_parts(message: BaseMessage) -> List[Part]: ) gapic_part = GapicPart(function_call=function_call) parts = [Part._from_gapic(gapic_part)] + else: + parts = _convert_to_parts(message) elif isinstance(message, HumanMessage): role = "user" parts = _convert_to_parts(message) @@ -188,6 +208,15 @@ def _convert_to_parts(message: BaseMessage) -> List[Part]: f"Unexpected message with type {type(message)} at the position {i}." ) + if raw_system_message: + if role == "model": + raise ValueError( + "SystemMessage should be followed by a HumanMessage and " + "not by AIMessage." + ) + parts = _convert_to_parts(raw_system_message) + parts + raw_system_message = None + vertex_message = Content(role=role, parts=parts) vertex_messages.append(vertex_message) return vertex_messages @@ -258,6 +287,11 @@ class ChatVertexAI(_VertexAICommon, BaseChatModel): model_name: str = "chat-bison" "Underlying model name." examples: Optional[List[BaseMessage]] = None + convert_system_message_to_human: bool = False + """Whether to merge any leading SystemMessage into the following HumanMessage. + + Gemini does not support system messages; any unsupported messages will + raise an error.""" @classmethod def is_lc_serializable(self) -> bool: @@ -327,7 +361,11 @@ def _generate( msg_params["candidate_count"] = params.pop("candidate_count") if self._is_gemini_model: - history_gemini = _parse_chat_history_gemini(messages, project=self.project) + history_gemini = _parse_chat_history_gemini( + messages, + project=self.project, + convert_system_message_to_human=self.convert_system_message_to_human, + ) message = history_gemini.pop() chat = self.client.start_chat(history=history_gemini) @@ -396,7 +434,11 @@ async def _agenerate( msg_params["candidate_count"] = params.pop("candidate_count") if self._is_gemini_model: - history_gemini = _parse_chat_history_gemini(messages, project=self.project) + history_gemini = _parse_chat_history_gemini( + messages, + project=self.project, + convert_system_message_to_human=self.convert_system_message_to_human, + ) message = history_gemini.pop() chat = self.client.start_chat(history=history_gemini) # set param to `functions` until core tool/function calling implemented @@ -441,7 +483,11 @@ def _stream( ) -> Iterator[ChatGenerationChunk]: params = self._prepare_params(stop=stop, stream=True, **kwargs) if self._is_gemini_model: - history_gemini = _parse_chat_history_gemini(messages, project=self.project) + history_gemini = _parse_chat_history_gemini( + messages, + project=self.project, + convert_system_message_to_human=self.convert_system_message_to_human, + ) message = history_gemini.pop() chat = self.client.start_chat(history=history_gemini) # set param to `functions` until core tool/function calling implemented diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py b/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py index 2b280515635e5..030b484d06819 100644 --- a/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py +++ b/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py @@ -182,3 +182,36 @@ def test_vertexai_single_call_fails_no_message() -> None: str(exc_info.value) == "You should provide at least one message to start the chat!" ) + + +@pytest.mark.parametrize("model_name", ["gemini-pro"]) +def test_chat_vertexai_gemini_system_message_error(model_name: str) -> None: + model = ChatVertexAI(model_name=model_name) + text_question1, text_answer1 = "How much is 2+2?", "4" + text_question2 = "How much is 3+3?" + system_message = SystemMessage(content="You're supposed to answer math questions.") + message1 = HumanMessage(content=text_question1) + message2 = AIMessage(content=text_answer1) + message3 = HumanMessage(content=text_question2) + with pytest.raises(ValueError): + model([system_message, message1, message2, message3]) + + +@pytest.mark.parametrize("model_name", model_names_to_test) +def test_chat_vertexai_system_message(model_name: str) -> None: + if model_name: + model = ChatVertexAI( + model_name=model_name, convert_system_message_to_human=True + ) + else: + model = ChatVertexAI() + + text_question1, text_answer1 = "How much is 2+2?", "4" + text_question2 = "How much is 3+3?" + system_message = SystemMessage(content="You're supposed to answer math questions.") + message1 = HumanMessage(content=text_question1) + message2 = AIMessage(content=text_answer1) + message3 = HumanMessage(content=text_question2) + response = model([system_message, message1, message2, message3]) + assert isinstance(response, AIMessage) + assert isinstance(response.content, str) diff --git a/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py b/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py index d11a970d65423..caed17118ab06 100644 --- a/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py +++ b/libs/partners/google-vertexai/tests/unit_tests/test_chat_models.py @@ -13,6 +13,7 @@ from langchain_google_vertexai.chat_models import ( ChatVertexAI, _parse_chat_history, + _parse_chat_history_gemini, _parse_examples, ) @@ -112,6 +113,24 @@ def test_parse_chat_history_correct() -> None: ] +def test_parse_history_gemini() -> None: + system_input = "You're supposed to answer math questions." + text_question1, text_answer1 = "How much is 2+2?", "4" + text_question2 = "How much is 3+3?" + system_message = SystemMessage(content=system_input) + message1 = HumanMessage(content=text_question1) + message2 = AIMessage(content=text_answer1) + message3 = HumanMessage(content=text_question2) + messages = [system_message, message1, message2, message3] + history = _parse_chat_history_gemini(messages, convert_system_message_to_human=True) + assert len(history) == 3 + assert history[0].role == "user" + assert history[0].parts[0].text == system_input + assert history[0].parts[1].text == text_question1 + assert history[1].role == "model" + assert history[1].parts[0].text == text_answer1 + + def test_default_params_palm() -> None: user_prompt = "Hello" From f60f59d69f25f746f3494bb8f7d16168ae4b079a Mon Sep 17 00:00:00 2001 From: Harrison Chase <hw.chase.17@gmail.com> Date: Thu, 18 Jan 2024 12:17:40 -0800 Subject: [PATCH 090/215] google-vertexai[patch]: Harrison/vertex function calling (#16223) Co-authored-by: Erick Friis <erick@langchain.dev> --- libs/partners/google-vertexai/Makefile | 4 +- .../langchain_google_vertexai/__init__.py | 4 + .../langchain_google_vertexai/chains.py | 111 ++++++++++++++++++ .../functions_utils.py | 104 +++++++++++++++- .../tests/unit_tests/test_imports.py | 2 + 5 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 libs/partners/google-vertexai/langchain_google_vertexai/chains.py diff --git a/libs/partners/google-vertexai/Makefile b/libs/partners/google-vertexai/Makefile index a1a4607ae611a..29214d4bbc70d 100644 --- a/libs/partners/google-vertexai/Makefile +++ b/libs/partners/google-vertexai/Makefile @@ -6,9 +6,9 @@ all: help # Define a variable for the test file path. TEST_FILE ?= tests/unit_tests/ -test_integration: TEST_FILE = tests/integration_tests/ +integration_tests: TEST_FILE = tests/integration_tests/ -test test_integration: +test integration_tests: poetry run pytest $(TEST_FILE) tests: diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py b/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py index ba97adf52e839..be365bde4c33a 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/__init__.py @@ -1,6 +1,8 @@ from langchain_google_vertexai._enums import HarmBlockThreshold, HarmCategory +from langchain_google_vertexai.chains import create_structured_runnable from langchain_google_vertexai.chat_models import ChatVertexAI from langchain_google_vertexai.embeddings import VertexAIEmbeddings +from langchain_google_vertexai.functions_utils import PydanticFunctionsOutputParser from langchain_google_vertexai.llms import VertexAI, VertexAIModelGarden __all__ = [ @@ -10,4 +12,6 @@ "VertexAIModelGarden", "HarmBlockThreshold", "HarmCategory", + "PydanticFunctionsOutputParser", + "create_structured_runnable", ] diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/chains.py b/libs/partners/google-vertexai/langchain_google_vertexai/chains.py new file mode 100644 index 0000000000000..9b11794b30936 --- /dev/null +++ b/libs/partners/google-vertexai/langchain_google_vertexai/chains.py @@ -0,0 +1,111 @@ +from typing import ( + Dict, + Optional, + Sequence, + Type, + Union, +) + +from langchain_core.output_parsers import ( + BaseGenerationOutputParser, + BaseOutputParser, +) +from langchain_core.prompts import BasePromptTemplate +from langchain_core.pydantic_v1 import BaseModel +from langchain_core.runnables import Runnable + +from langchain_google_vertexai.functions_utils import PydanticFunctionsOutputParser + + +def get_output_parser( + functions: Sequence[Type[BaseModel]], +) -> Union[BaseOutputParser, BaseGenerationOutputParser]: + """Get the appropriate function output parser given the user functions. + + Args: + functions: Sequence where element is a dictionary, a pydantic.BaseModel class, + or a Python function. If a dictionary is passed in, it is assumed to + already be a valid OpenAI function. + + Returns: + A PydanticFunctionsOutputParser + """ + function_names = [f.__name__ for f in functions] + if len(functions) > 1: + pydantic_schema: Union[Dict, Type[BaseModel]] = { + name: fn for name, fn in zip(function_names, functions) + } + else: + pydantic_schema = functions[0] + output_parser: Union[ + BaseOutputParser, BaseGenerationOutputParser + ] = PydanticFunctionsOutputParser(pydantic_schema=pydantic_schema) + return output_parser + + +def create_structured_runnable( + function: Union[Type[BaseModel], Sequence[Type[BaseModel]]], + llm: Runnable, + *, + prompt: Optional[BasePromptTemplate] = None, +) -> Runnable: + """Create a runnable sequence that uses OpenAI functions. + + Args: + function: Either a single pydantic.BaseModel class or a sequence + of pydantic.BaseModels classes. + For best results, pydantic.BaseModels + should have descriptions of the parameters. + llm: Language model to use, + assumed to support the Google Vertex function-calling API. + prompt: BasePromptTemplate to pass to the model. + + Returns: + A runnable sequence that will pass in the given functions to the model when run. + + Example: + .. code-block:: python + + from typing import Optional + + from langchain_google_vertexai import ChatVertexAI, create_structured_runnable + from langchain_core.prompts import ChatPromptTemplate + from langchain_core.pydantic_v1 import BaseModel, Field + + + class RecordPerson(BaseModel): + \"\"\"Record some identifying information about a person.\"\"\" + + name: str = Field(..., description="The person's name") + age: int = Field(..., description="The person's age") + fav_food: Optional[str] = Field(None, description="The person's favorite food") + + + class RecordDog(BaseModel): + \"\"\"Record some identifying information about a dog.\"\"\" + + name: str = Field(..., description="The dog's name") + color: str = Field(..., description="The dog's color") + fav_food: Optional[str] = Field(None, description="The dog's favorite food") + + + llm = ChatVertexAI(model_name="gemini-pro") + prompt = ChatPromptTemplate.from_template(\"\"\" + You are a world class algorithm for recording entities. + Make calls to the relevant function to record the entities in the following input: {input} + Tip: Make sure to answer in the correct format\"\"\" + ) + chain = create_structured_runnable([RecordPerson, RecordDog], llm, prompt=prompt) + chain.invoke({"input": "Harry was a chubby brown beagle who loved chicken"}) + # -> RecordDog(name="Harry", color="brown", fav_food="chicken") + """ # noqa: E501 + if not function: + raise ValueError("Need to pass in at least one function. Received zero.") + functions = function if isinstance(function, Sequence) else [function] + output_parser = get_output_parser(functions) + llm_with_functions = llm.bind(functions=functions) + if prompt is None: + initial_chain = llm_with_functions + else: + initial_chain = prompt | llm_with_functions + return initial_chain | output_parser diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py index 8e6aed3da1951..304e6a85c14ec 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py @@ -1,5 +1,10 @@ -from typing import List +import json +from typing import Dict, List, Type, Union +from langchain_core.exceptions import OutputParserException +from langchain_core.output_parsers import BaseOutputParser +from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.pydantic_v1 import BaseModel from langchain_core.tools import Tool from langchain_core.utils.function_calling import FunctionDescription from langchain_core.utils.json_schema import dereference_refs @@ -11,6 +16,29 @@ ) +def _format_pydantic_to_vertex_function( + pydantic_model: Type[BaseModel], +) -> FunctionDescription: + schema = dereference_refs(pydantic_model.schema()) + schema.pop("definitions", None) + + return { + "name": schema["title"], + "description": schema["description"], + "parameters": { + "properties": { + k: { + "type": v["type"], + "description": v.get("description"), + } + for k, v in schema["properties"].items() + }, + "required": schema["required"], + "type": schema["type"], + }, + } + + def _format_tool_to_vertex_function(tool: Tool) -> FunctionDescription: "Format tool into the Vertex function API." if tool.args_schema: @@ -46,11 +74,81 @@ def _format_tool_to_vertex_function(tool: Tool) -> FunctionDescription: } -def _format_tools_to_vertex_tool(tools: List[Tool]) -> List[VertexTool]: +def _format_tools_to_vertex_tool( + tools: List[Union[Tool, Type[BaseModel]]], +) -> List[VertexTool]: "Format tool into the Vertex Tool instance." function_declarations = [] for tool in tools: - func = _format_tool_to_vertex_function(tool) + if isinstance(tool, Tool): + func = _format_tool_to_vertex_function(tool) + else: + func = _format_pydantic_to_vertex_function(tool) function_declarations.append(FunctionDeclaration(**func)) return [VertexTool(function_declarations=function_declarations)] + + +class PydanticFunctionsOutputParser(BaseOutputParser): + """Parse an output as a pydantic object. + + This parser is used to parse the output of a ChatModel that uses + Google Vertex function format to invoke functions. + + The parser extracts the function call invocation and matches + them to the pydantic schema provided. + + An exception will be raised if the function call does not match + the provided schema. + + Example: + + ... code-block:: python + + message = AIMessage( + content="This is a test message", + additional_kwargs={ + "function_call": { + "name": "cookie", + "arguments": json.dumps({"name": "value", "age": 10}), + } + }, + ) + chat_generation = ChatGeneration(message=message) + + class Cookie(BaseModel): + name: str + age: int + + class Dog(BaseModel): + species: str + + # Full output + parser = PydanticOutputFunctionsParser( + pydantic_schema={"cookie": Cookie, "dog": Dog} + ) + result = parser.parse_result([chat_generation]) + """ + + pydantic_schema: Union[Type[BaseModel], Dict[str, Type[BaseModel]]] + + def parse_result( + self, result: List[Generation], *, partial: bool = False + ) -> BaseModel: + if not isinstance(result[0], ChatGeneration): + raise ValueError("This output parser only works on ChatGeneration output") + message = result[0].message + function_call = message.additional_kwargs.get("function_call", {}) + if function_call: + function_name = function_call["name"] + tool_input = function_call.get("arguments", {}) + if isinstance(self.pydantic_schema, dict): + schema = self.pydantic_schema[function_name] + else: + schema = self.pydantic_schema + return schema(**json.loads(tool_input)) + else: + raise OutputParserException(f"Could not parse function call: {message}") + + def parse(self, text: str) -> BaseModel: + raise ValueError("Can only parse messages") diff --git a/libs/partners/google-vertexai/tests/unit_tests/test_imports.py b/libs/partners/google-vertexai/tests/unit_tests/test_imports.py index 11e91afcbe0c4..7afa74f1dc7ce 100644 --- a/libs/partners/google-vertexai/tests/unit_tests/test_imports.py +++ b/libs/partners/google-vertexai/tests/unit_tests/test_imports.py @@ -7,6 +7,8 @@ "VertexAIModelGarden", "HarmBlockThreshold", "HarmCategory", + "PydanticFunctionsOutputParser", + "create_structured_runnable", ] From f2b2d59e82522463ebfcedec976e0ca8ec0ea7c4 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 12:23:04 -0800 Subject: [PATCH 091/215] docs: transport and client options docs (#16226) <!-- Thank you for contributing to LangChain! Please title your PR "<package>: <description>", where <package> is whichever of langchain, community, core, experimental, etc. is being modified. Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes if applicable, - **Dependencies:** any dependencies required for this change, - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` from the root of the package you've modified to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ 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. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> --- .../chat/google_generative_ai.ipynb | 17 ++++++++++++++++- .../text_embedding/google_generative_ai.ipynb | 13 +++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/docs/integrations/chat/google_generative_ai.ipynb b/docs/docs/integrations/chat/google_generative_ai.ipynb index fe718d7a7a728..c8544163a4f4c 100644 --- a/docs/docs/integrations/chat/google_generative_ai.ipynb +++ b/docs/docs/integrations/chat/google_generative_ai.ipynb @@ -320,11 +320,26 @@ "4. Message may be blocked if they violate the safety checks of the LLM. In this case, the model will return an empty response." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "75fdfad6", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "92b5aca5", "metadata": {}, - "source": [] + "source": [ + "## Additional Configuraation\n", + "\n", + "You can pass the following parameters to ChatGoogleGenerativeAI in order to customize the SDK's behavior:\n", + "\n", + "- `client_options`: [Client Options](https://googleapis.dev/python/google-api-core/latest/client_options.html#module-google.api_core.client_options) to pass to the Google API Client, such as a custom `client_options[\"api_endpoint\"]`\n", + "- `transport`: The transport method to use, such as `rest`, `grpc`, or `grpc_asyncio`." + ] } ], "metadata": { diff --git a/docs/docs/integrations/text_embedding/google_generative_ai.ipynb b/docs/docs/integrations/text_embedding/google_generative_ai.ipynb index 48e8a47522c1b..7cac7e42b8f05 100644 --- a/docs/docs/integrations/text_embedding/google_generative_ai.ipynb +++ b/docs/docs/integrations/text_embedding/google_generative_ai.ipynb @@ -194,6 +194,19 @@ "source": [ "In retrieval, relative distance matters. In the image above, you can see the difference in similarity scores between the \"relevant doc\" and \"simil stronger delta between the similar query and relevant doc on the latter case." ] + }, + { + "cell_type": "markdown", + "id": "2e7857e5", + "metadata": {}, + "source": [ + "## Additional Configuraation\n", + "\n", + "You can pass the following parameters to ChatGoogleGenerativeAI in order to customize the SDK's behavior:\n", + "\n", + "- `client_options`: [Client Options](https://googleapis.dev/python/google-api-core/latest/client_options.html#module-google.api_core.client_options) to pass to the Google API Client, such as a custom `client_options[\"api_endpoint\"]`\n", + "- `transport`: The transport method to use, such as `rest`, `grpc`, or `grpc_asyncio`." + ] } ], "metadata": { From aa35b43bcd3ea5905ef03367ee26a55ecfc93b1d Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 13:15:09 -0800 Subject: [PATCH 092/215] docs, google-vertex[patch]: function docs (#16231) --- .../chat/google_vertex_ai_palm.ipynb | 59 ++++++++++++++----- .../functions_utils.py | 2 +- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb b/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb index 050a32f2bf63c..008746f747946 100644 --- a/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb +++ b/docs/docs/integrations/chat/google_vertex_ai_palm.ipynb @@ -34,28 +34,18 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "^C\n", - "\u001b[31mERROR: Operation cancelled by user\u001b[0m\u001b[31m\n", - "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" - ] - } - ], + "outputs": [], "source": [ "%pip install --upgrade --quiet langchain-google-vertexai" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -352,6 +342,47 @@ "pprint(result.generations[0][0].generation_info)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Function Calling with Gemini\n", + "\n", + "We can call Gemini models with tools." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "MyModel(name='Erick', age=27)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.pydantic_v1 import BaseModel\n", + "from langchain_google_vertexai import create_structured_runnable\n", + "\n", + "llm = ChatVertexAI(model_name=\"gemini-pro\")\n", + "\n", + "\n", + "class MyModel(BaseModel):\n", + " name: str\n", + " age: int\n", + "\n", + "\n", + "chain = create_structured_runnable(MyModel, llm)\n", + "chain.invoke(\"My name is Erick and I'm 27 years old\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -386,7 +417,7 @@ "AIMessage(content=' Why do you love programming?')" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py index 304e6a85c14ec..d1786ae67865e 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py @@ -24,7 +24,7 @@ def _format_pydantic_to_vertex_function( return { "name": schema["title"], - "description": schema["description"], + "description": schema.get("description", ""), "parameters": { "properties": { k: { From 0e76d841374ef2fa8d88c647fbda0a885a7d2b0f Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 13:59:23 -0800 Subject: [PATCH 093/215] google-vertexai[patch]: more integration test fixes (#16234) --- .../langchain_google_vertexai/llms.py | 11 +++++++---- .../tests/integration_tests/test_chat_models.py | 9 +++++++-- .../tests/integration_tests/test_tools.py | 12 ++++++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py index a71e6a0736eae..cfb5f17426c5e 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/llms.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/llms.py @@ -319,11 +319,14 @@ def _response_to_generation( ) -> GenerationChunk: """Converts a stream response to a generation chunk.""" generation_info = get_generation_info(response, self._is_gemini_model) - + try: + text = response.text + except AttributeError: + text = "" + except ValueError: + text = "" return GenerationChunk( - text=response.text - if hasattr(response, "text") - else "", # might not exist if blocked + text=text, generation_info=generation_info, ) diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py b/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py index 030b484d06819..a29094bf920d2 100644 --- a/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py +++ b/libs/partners/google-vertexai/tests/integration_tests/test_chat_models.py @@ -66,9 +66,13 @@ async def test_vertexai_agenerate(model_name: str) -> None: async_generation = cast(ChatGeneration, response.generations[0][0]) # assert some properties to make debugging easier - assert sync_generation.message.content == async_generation.message.content + + # xfail: this is not equivalent with temp=0 right now + # assert sync_generation.message.content == async_generation.message.content assert sync_generation.generation_info == async_generation.generation_info - assert sync_generation == async_generation + + # xfail: content is not same right now + # assert sync_generation == async_generation @pytest.mark.parametrize("model_name", ["chat-bison@001", "gemini-pro"]) @@ -116,6 +120,7 @@ def test_multimodal() -> None: assert isinstance(output.content, str) +@pytest.mark.xfail(reason="problem on vertex side") def test_multimodal_history() -> None: llm = ChatVertexAI(model_name="gemini-pro-vision") gcs_url = ( diff --git a/libs/partners/google-vertexai/tests/integration_tests/test_tools.py b/libs/partners/google-vertexai/tests/integration_tests/test_tools.py index dda715879d923..3230d002db7c5 100644 --- a/libs/partners/google-vertexai/tests/integration_tests/test_tools.py +++ b/libs/partners/google-vertexai/tests/integration_tests/test_tools.py @@ -1,4 +1,5 @@ import os +import re from typing import List, Union from langchain_core.agents import AgentAction, AgentActionMessageLog, AgentFinish @@ -83,7 +84,12 @@ def test_tools() -> None: print(response) assert isinstance(response, dict) assert response["input"] == "What is 6 raised to the 0.43 power?" - assert round(float(response["output"]), 3) == 2.161 + + # convert string " The result is 2.160752567226312" to just numbers/periods + # use regex to find \d+\.\d+ + just_numbers = re.findall(r"\d+\.\d+", response["output"])[0] + + assert round(float(just_numbers), 3) == 2.161 def test_stream() -> None: @@ -163,4 +169,6 @@ def test_multiple_tools() -> None: response = agent_executor.invoke({"input": question}) assert isinstance(response, dict) assert response["input"] == question - assert "3.850" in response["output"] + + # xfail: not getting age in search result most of time + # assert "3.850" in response["output"] From 92bc80483a2b7b0356ac19059fd0a972f4e349d7 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 14:06:38 -0800 Subject: [PATCH 094/215] infra: google search api key (#16237) --- .github/workflows/_integration_test.yml | 1 + .github/workflows/_release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index 030883e555827..586ac577dfea5 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -51,6 +51,7 @@ jobs: MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }} run: | make integration_tests diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 5de6107520850..923a647bb54c8 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -170,6 +170,7 @@ jobs: MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }} run: make integration_tests working-directory: ${{ inputs.working-directory }} From eec334793902880f09a4310c92a83808a1030d5f Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 14:07:19 -0800 Subject: [PATCH 095/215] docs: together cookbook import (#16236) --- cookbook/together_ai.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/together_ai.ipynb b/cookbook/together_ai.ipynb index 1266073e83cc2..ed6dd906a2fe0 100644 --- a/cookbook/together_ai.ipynb +++ b/cookbook/together_ai.ipynb @@ -82,7 +82,7 @@ "prompt = ChatPromptTemplate.from_template(template)\n", "\n", "# LLM\n", - "from langchain_community.llms import Together\n", + "from langchain_together import Together\n", "\n", "llm = Together(\n", " model=\"mistralai/Mixtral-8x7B-Instruct-v0.1\",\n", From b9495da92dce615be2e99c7a0db10670a3ad9a0b Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 14:07:44 -0800 Subject: [PATCH 096/215] langchain[patch]: fix stuff documents chain api docs render (#16159) --- .../chains/combine_documents/stuff.py | 2 +- pg_essay.txt | 351 ++++++++++++++++++ 2 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 pg_essay.txt diff --git a/libs/langchain/langchain/chains/combine_documents/stuff.py b/libs/langchain/langchain/chains/combine_documents/stuff.py index d965eb4219754..2dfdf80cf8406 100644 --- a/libs/langchain/langchain/chains/combine_documents/stuff.py +++ b/libs/langchain/langchain/chains/combine_documents/stuff.py @@ -58,7 +58,7 @@ def create_stuff_documents_chain( from langchain.chains.combine_documents import create_stuff_documents_chain prompt = ChatPromptTemplate.from_messages( - [("system", "What are everyone's favorite colors:\n\n{context}")] + [("system", "What are everyone's favorite colors:\\n\\n{context}")] ) llm = ChatOpenAI(model_name="gpt-3.5-turbo") chain = create_stuff_documents_chain(llm, prompt) diff --git a/pg_essay.txt b/pg_essay.txt new file mode 100644 index 0000000000000..0bce3830b9968 --- /dev/null +++ b/pg_essay.txt @@ -0,0 +1,351 @@ +What I Worked On + +February 2021 + +Before college the two main things I worked on, outside of school, were writing and programming. I didn't write essays. I wrote what beginning writers were supposed to write then, and probably still are: short stories. My stories were awful. They had hardly any plot, just characters with strong feelings, which I imagined made them deep. + +The first programs I tried writing were on the IBM 1401 that our school district used for what was then called "data processing." This was in 9th grade, so I was 13 or 14. The school district's 1401 happened to be in the basement of our junior high school, and my friend Rich Draves and I got permission to use it. It was like a mini Bond villain's lair down there, with all these alien-looking machines — CPU, disk drives, printer, card reader — sitting up on a raised floor under bright fluorescent lights. + +The language we used was an early version of Fortran. You had to type programs on punch cards, then stack them in the card reader and press a button to load the program into memory and run it. The result would ordinarily be to print something on the spectacularly loud printer. + +I was puzzled by the 1401. I couldn't figure out what to do with it. And in retrospect there's not much I could have done with it. The only form of input to programs was data stored on punched cards, and I didn't have any data stored on punched cards. The only other option was to do things that didn't rely on any input, like calculate approximations of pi, but I didn't know enough math to do anything interesting of that type. So I'm not surprised I can't remember any programs I wrote, because they can't have done much. My clearest memory is of the moment I learned it was possible for programs not to terminate, when one of mine didn't. On a machine without time-sharing, this was a social as well as a technical error, as the data center manager's expression made clear. + +With microcomputers, everything changed. Now you could have a computer sitting right in front of you, on a desk, that could respond to your keystrokes as it was running instead of just churning through a stack of punch cards and then stopping. [1] + +The first of my friends to get a microcomputer built it himself. It was sold as a kit by Heathkit. I remember vividly how impressed and envious I felt watching him sitting in front of it, typing programs right into the computer. + +Computers were expensive in those days and it took me years of nagging before I convinced my father to buy one, a TRS-80, in about 1980. The gold standard then was the Apple II, but a TRS-80 was good enough. This was when I really started programming. I wrote simple games, a program to predict how high my model rockets would fly, and a word processor that my father used to write at least one book. There was only room in memory for about 2 pages of text, so he'd write 2 pages at a time and then print them out, but it was a lot better than a typewriter. + +Though I liked programming, I didn't plan to study it in college. In college I was going to study philosophy, which sounded much more powerful. It seemed, to my naive high school self, to be the study of the ultimate truths, compared to which the things studied in other fields would be mere domain knowledge. What I discovered when I got to college was that the other fields took up so much of the space of ideas that there wasn't much left for these supposed ultimate truths. All that seemed left for philosophy were edge cases that people in other fields felt could safely be ignored. + +I couldn't have put this into words when I was 18. All I knew at the time was that I kept taking philosophy courses and they kept being boring. So I decided to switch to AI. + +AI was in the air in the mid 1980s, but there were two things especially that made me want to work on it: a novel by Heinlein called The Moon is a Harsh Mistress, which featured an intelligent computer called Mike, and a PBS documentary that showed Terry Winograd using SHRDLU. I haven't tried rereading The Moon is a Harsh Mistress, so I don't know how well it has aged, but when I read it I was drawn entirely into its world. It seemed only a matter of time before we'd have Mike, and when I saw Winograd using SHRDLU, it seemed like that time would be a few years at most. All you had to do was teach SHRDLU more words. + +There weren't any classes in AI at Cornell then, not even graduate classes, so I started trying to teach myself. Which meant learning Lisp, since in those days Lisp was regarded as the language of AI. The commonly used programming languages then were pretty primitive, and programmers' ideas correspondingly so. The default language at Cornell was a Pascal-like language called PL/I, and the situation was similar elsewhere. Learning Lisp expanded my concept of a program so fast that it was years before I started to have a sense of where the new limits were. This was more like it; this was what I had expected college to do. It wasn't happening in a class, like it was supposed to, but that was ok. For the next couple years I was on a roll. I knew what I was going to do. + +For my undergraduate thesis, I reverse-engineered SHRDLU. My God did I love working on that program. It was a pleasing bit of code, but what made it even more exciting was my belief — hard to imagine now, but not unique in 1985 — that it was already climbing the lower slopes of intelligence. + +I had gotten into a program at Cornell that didn't make you choose a major. You could take whatever classes you liked, and choose whatever you liked to put on your degree. I of course chose "Artificial Intelligence." When I got the actual physical diploma, I was dismayed to find that the quotes had been included, which made them read as scare-quotes. At the time this bothered me, but now it seems amusingly accurate, for reasons I was about to discover. + +I applied to 3 grad schools: MIT and Yale, which were renowned for AI at the time, and Harvard, which I'd visited because Rich Draves went there, and was also home to Bill Woods, who'd invented the type of parser I used in my SHRDLU clone. Only Harvard accepted me, so that was where I went. + +I don't remember the moment it happened, or if there even was a specific moment, but during the first year of grad school I realized that AI, as practiced at the time, was a hoax. By which I mean the sort of AI in which a program that's told "the dog is sitting on the chair" translates this into some formal representation and adds it to the list of things it knows. + +What these programs really showed was that there's a subset of natural language that's a formal language. But a very proper subset. It was clear that there was an unbridgeable gap between what they could do and actually understanding natural language. It was not, in fact, simply a matter of teaching SHRDLU more words. That whole way of doing AI, with explicit data structures representing concepts, was not going to work. Its brokenness did, as so often happens, generate a lot of opportunities to write papers about various band-aids that could be applied to it, but it was never going to get us Mike. + +So I looked around to see what I could salvage from the wreckage of my plans, and there was Lisp. I knew from experience that Lisp was interesting for its own sake and not just for its association with AI, even though that was the main reason people cared about it at the time. So I decided to focus on Lisp. In fact, I decided to write a book about Lisp hacking. It's scary to think how little I knew about Lisp hacking when I started writing that book. But there's nothing like writing a book about something to help you learn it. The book, On Lisp, wasn't published till 1993, but I wrote much of it in grad school. + +Computer Science is an uneasy alliance between two halves, theory and systems. The theory people prove things, and the systems people build things. I wanted to build things. I had plenty of respect for theory — indeed, a sneaking suspicion that it was the more admirable of the two halves — but building things seemed so much more exciting. + +The problem with systems work, though, was that it didn't last. Any program you wrote today, no matter how good, would be obsolete in a couple decades at best. People might mention your software in footnotes, but no one would actually use it. And indeed, it would seem very feeble work. Only people with a sense of the history of the field would even realize that, in its time, it had been good. + +There were some surplus Xerox Dandelions floating around the computer lab at one point. Anyone who wanted one to play around with could have one. I was briefly tempted, but they were so slow by present standards; what was the point? No one else wanted one either, so off they went. That was what happened to systems work. + +I wanted not just to build things, but to build things that would last. + +In this dissatisfied state I went in 1988 to visit Rich Draves at CMU, where he was in grad school. One day I went to visit the Carnegie Institute, where I'd spent a lot of time as a kid. While looking at a painting there I realized something that might seem obvious, but was a big surprise to me. There, right on the wall, was something you could make that would last. Paintings didn't become obsolete. Some of the best ones were hundreds of years old. + +And moreover this was something you could make a living doing. Not as easily as you could by writing software, of course, but I thought if you were really industrious and lived really cheaply, it had to be possible to make enough to survive. And as an artist you could be truly independent. You wouldn't have a boss, or even need to get research funding. + +I had always liked looking at paintings. Could I make them? I had no idea. I'd never imagined it was even possible. I knew intellectually that people made art — that it didn't just appear spontaneously — but it was as if the people who made it were a different species. They either lived long ago or were mysterious geniuses doing strange things in profiles in Life magazine. The idea of actually being able to make art, to put that verb before that noun, seemed almost miraculous. + +That fall I started taking art classes at Harvard. Grad students could take classes in any department, and my advisor, Tom Cheatham, was very easy going. If he even knew about the strange classes I was taking, he never said anything. + +So now I was in a PhD program in computer science, yet planning to be an artist, yet also genuinely in love with Lisp hacking and working away at On Lisp. In other words, like many a grad student, I was working energetically on multiple projects that were not my thesis. + +I didn't see a way out of this situation. I didn't want to drop out of grad school, but how else was I going to get out? I remember when my friend Robert Morris got kicked out of Cornell for writing the internet worm of 1988, I was envious that he'd found such a spectacular way to get out of grad school. + +Then one day in April 1990 a crack appeared in the wall. I ran into professor Cheatham and he asked if I was far enough along to graduate that June. I didn't have a word of my dissertation written, but in what must have been the quickest bit of thinking in my life, I decided to take a shot at writing one in the 5 weeks or so that remained before the deadline, reusing parts of On Lisp where I could, and I was able to respond, with no perceptible delay "Yes, I think so. I'll give you something to read in a few days." + +I picked applications of continuations as the topic. In retrospect I should have written about macros and embedded languages. There's a whole world there that's barely been explored. But all I wanted was to get out of grad school, and my rapidly written dissertation sufficed, just barely. + +Meanwhile I was applying to art schools. I applied to two: RISD in the US, and the Accademia di Belli Arti in Florence, which, because it was the oldest art school, I imagined would be good. RISD accepted me, and I never heard back from the Accademia, so off to Providence I went. + +I'd applied for the BFA program at RISD, which meant in effect that I had to go to college again. This was not as strange as it sounds, because I was only 25, and art schools are full of people of different ages. RISD counted me as a transfer sophomore and said I had to do the foundation that summer. The foundation means the classes that everyone has to take in fundamental subjects like drawing, color, and design. + +Toward the end of the summer I got a big surprise: a letter from the Accademia, which had been delayed because they'd sent it to Cambridge England instead of Cambridge Massachusetts, inviting me to take the entrance exam in Florence that fall. This was now only weeks away. My nice landlady let me leave my stuff in her attic. I had some money saved from consulting work I'd done in grad school; there was probably enough to last a year if I lived cheaply. Now all I had to do was learn Italian. + +Only stranieri (foreigners) had to take this entrance exam. In retrospect it may well have been a way of excluding them, because there were so many stranieri attracted by the idea of studying art in Florence that the Italian students would otherwise have been outnumbered. I was in decent shape at painting and drawing from the RISD foundation that summer, but I still don't know how I managed to pass the written exam. I remember that I answered the essay question by writing about Cezanne, and that I cranked up the intellectual level as high as I could to make the most of my limited vocabulary. [2] + +I'm only up to age 25 and already there are such conspicuous patterns. Here I was, yet again about to attend some august institution in the hopes of learning about some prestigious subject, and yet again about to be disappointed. The students and faculty in the painting department at the Accademia were the nicest people you could imagine, but they had long since arrived at an arrangement whereby the students wouldn't require the faculty to teach anything, and in return the faculty wouldn't require the students to learn anything. And at the same time all involved would adhere outwardly to the conventions of a 19th century atelier. We actually had one of those little stoves, fed with kindling, that you see in 19th century studio paintings, and a nude model sitting as close to it as possible without getting burned. Except hardly anyone else painted her besides me. The rest of the students spent their time chatting or occasionally trying to imitate things they'd seen in American art magazines. + +Our model turned out to live just down the street from me. She made a living from a combination of modelling and making fakes for a local antique dealer. She'd copy an obscure old painting out of a book, and then he'd take the copy and maltreat it to make it look old. [3] + +While I was a student at the Accademia I started painting still lives in my bedroom at night. These paintings were tiny, because the room was, and because I painted them on leftover scraps of canvas, which was all I could afford at the time. Painting still lives is different from painting people, because the subject, as its name suggests, can't move. People can't sit for more than about 15 minutes at a time, and when they do they don't sit very still. So the traditional m.o. for painting people is to know how to paint a generic person, which you then modify to match the specific person you're painting. Whereas a still life you can, if you want, copy pixel by pixel from what you're seeing. You don't want to stop there, of course, or you get merely photographic accuracy, and what makes a still life interesting is that it's been through a head. You want to emphasize the visual cues that tell you, for example, that the reason the color changes suddenly at a certain point is that it's the edge of an object. By subtly emphasizing such things you can make paintings that are more realistic than photographs not just in some metaphorical sense, but in the strict information-theoretic sense. [4] + +I liked painting still lives because I was curious about what I was seeing. In everyday life, we aren't consciously aware of much we're seeing. Most visual perception is handled by low-level processes that merely tell your brain "that's a water droplet" without telling you details like where the lightest and darkest points are, or "that's a bush" without telling you the shape and position of every leaf. This is a feature of brains, not a bug. In everyday life it would be distracting to notice every leaf on every bush. But when you have to paint something, you have to look more closely, and when you do there's a lot to see. You can still be noticing new things after days of trying to paint something people usually take for granted, just as you can after days of trying to write an essay about something people usually take for granted. + +This is not the only way to paint. I'm not 100% sure it's even a good way to paint. But it seemed a good enough bet to be worth trying. + +Our teacher, professor Ulivi, was a nice guy. He could see I worked hard, and gave me a good grade, which he wrote down in a sort of passport each student had. But the Accademia wasn't teaching me anything except Italian, and my money was running out, so at the end of the first year I went back to the US. + +I wanted to go back to RISD, but I was now broke and RISD was very expensive, so I decided to get a job for a year and then return to RISD the next fall. I got one at a company called Interleaf, which made software for creating documents. You mean like Microsoft Word? Exactly. That was how I learned that low end software tends to eat high end software. But Interleaf still had a few years to live yet. [5] + +Interleaf had done something pretty bold. Inspired by Emacs, they'd added a scripting language, and even made the scripting language a dialect of Lisp. Now they wanted a Lisp hacker to write things in it. This was the closest thing I've had to a normal job, and I hereby apologize to my boss and coworkers, because I was a bad employee. Their Lisp was the thinnest icing on a giant C cake, and since I didn't know C and didn't want to learn it, I never understood most of the software. Plus I was terribly irresponsible. This was back when a programming job meant showing up every day during certain working hours. That seemed unnatural to me, and on this point the rest of the world is coming around to my way of thinking, but at the time it caused a lot of friction. Toward the end of the year I spent much of my time surreptitiously working on On Lisp, which I had by this time gotten a contract to publish. + +The good part was that I got paid huge amounts of money, especially by art student standards. In Florence, after paying my part of the rent, my budget for everything else had been $7 a day. Now I was getting paid more than 4 times that every hour, even when I was just sitting in a meeting. By living cheaply I not only managed to save enough to go back to RISD, but also paid off my college loans. + +I learned some useful things at Interleaf, though they were mostly about what not to do. I learned that it's better for technology companies to be run by product people than sales people (though sales is a real skill and people who are good at it are really good at it), that it leads to bugs when code is edited by too many people, that cheap office space is no bargain if it's depressing, that planned meetings are inferior to corridor conversations, that big, bureaucratic customers are a dangerous source of money, and that there's not much overlap between conventional office hours and the optimal time for hacking, or conventional offices and the optimal place for it. + +But the most important thing I learned, and which I used in both Viaweb and Y Combinator, is that the low end eats the high end: that it's good to be the "entry level" option, even though that will be less prestigious, because if you're not, someone else will be, and will squash you against the ceiling. Which in turn means that prestige is a danger sign. + +When I left to go back to RISD the next fall, I arranged to do freelance work for the group that did projects for customers, and this was how I survived for the next several years. When I came back to visit for a project later on, someone told me about a new thing called HTML, which was, as he described it, a derivative of SGML. Markup language enthusiasts were an occupational hazard at Interleaf and I ignored him, but this HTML thing later became a big part of my life. + +In the fall of 1992 I moved back to Providence to continue at RISD. The foundation had merely been intro stuff, and the Accademia had been a (very civilized) joke. Now I was going to see what real art school was like. But alas it was more like the Accademia than not. Better organized, certainly, and a lot more expensive, but it was now becoming clear that art school did not bear the same relationship to art that medical school bore to medicine. At least not the painting department. The textile department, which my next door neighbor belonged to, seemed to be pretty rigorous. No doubt illustration and architecture were too. But painting was post-rigorous. Painting students were supposed to express themselves, which to the more worldly ones meant to try to cook up some sort of distinctive signature style. + +A signature style is the visual equivalent of what in show business is known as a "schtick": something that immediately identifies the work as yours and no one else's. For example, when you see a painting that looks like a certain kind of cartoon, you know it's by Roy Lichtenstein. So if you see a big painting of this type hanging in the apartment of a hedge fund manager, you know he paid millions of dollars for it. That's not always why artists have a signature style, but it's usually why buyers pay a lot for such work. [6] + +There were plenty of earnest students too: kids who "could draw" in high school, and now had come to what was supposed to be the best art school in the country, to learn to draw even better. They tended to be confused and demoralized by what they found at RISD, but they kept going, because painting was what they did. I was not one of the kids who could draw in high school, but at RISD I was definitely closer to their tribe than the tribe of signature style seekers. + +I learned a lot in the color class I took at RISD, but otherwise I was basically teaching myself to paint, and I could do that for free. So in 1993 I dropped out. I hung around Providence for a bit, and then my college friend Nancy Parmet did me a big favor. A rent-controlled apartment in a building her mother owned in New York was becoming vacant. Did I want it? It wasn't much more than my current place, and New York was supposed to be where the artists were. So yes, I wanted it! [7] + +Asterix comics begin by zooming in on a tiny corner of Roman Gaul that turns out not to be controlled by the Romans. You can do something similar on a map of New York City: if you zoom in on the Upper East Side, there's a tiny corner that's not rich, or at least wasn't in 1993. It's called Yorkville, and that was my new home. Now I was a New York artist — in the strictly technical sense of making paintings and living in New York. + +I was nervous about money, because I could sense that Interleaf was on the way down. Freelance Lisp hacking work was very rare, and I didn't want to have to program in another language, which in those days would have meant C++ if I was lucky. So with my unerring nose for financial opportunity, I decided to write another book on Lisp. This would be a popular book, the sort of book that could be used as a textbook. I imagined myself living frugally off the royalties and spending all my time painting. (The painting on the cover of this book, ANSI Common Lisp, is one that I painted around this time.) + +The best thing about New York for me was the presence of Idelle and Julian Weber. Idelle Weber was a painter, one of the early photorealists, and I'd taken her painting class at Harvard. I've never known a teacher more beloved by her students. Large numbers of former students kept in touch with her, including me. After I moved to New York I became her de facto studio assistant. + +She liked to paint on big, square canvases, 4 to 5 feet on a side. One day in late 1994 as I was stretching one of these monsters there was something on the radio about a famous fund manager. He wasn't that much older than me, and was super rich. The thought suddenly occurred to me: why don't I become rich? Then I'll be able to work on whatever I want. + +Meanwhile I'd been hearing more and more about this new thing called the World Wide Web. Robert Morris showed it to me when I visited him in Cambridge, where he was now in grad school at Harvard. It seemed to me that the web would be a big deal. I'd seen what graphical user interfaces had done for the popularity of microcomputers. It seemed like the web would do the same for the internet. + +If I wanted to get rich, here was the next train leaving the station. I was right about that part. What I got wrong was the idea. I decided we should start a company to put art galleries online. I can't honestly say, after reading so many Y Combinator applications, that this was the worst startup idea ever, but it was up there. Art galleries didn't want to be online, and still don't, not the fancy ones. That's not how they sell. I wrote some software to generate web sites for galleries, and Robert wrote some to resize images and set up an http server to serve the pages. Then we tried to sign up galleries. To call this a difficult sale would be an understatement. It was difficult to give away. A few galleries let us make sites for them for free, but none paid us. + +Then some online stores started to appear, and I realized that except for the order buttons they were identical to the sites we'd been generating for galleries. This impressive-sounding thing called an "internet storefront" was something we already knew how to build. + +So in the summer of 1995, after I submitted the camera-ready copy of ANSI Common Lisp to the publishers, we started trying to write software to build online stores. At first this was going to be normal desktop software, which in those days meant Windows software. That was an alarming prospect, because neither of us knew how to write Windows software or wanted to learn. We lived in the Unix world. But we decided we'd at least try writing a prototype store builder on Unix. Robert wrote a shopping cart, and I wrote a new site generator for stores — in Lisp, of course. + +We were working out of Robert's apartment in Cambridge. His roommate was away for big chunks of time, during which I got to sleep in his room. For some reason there was no bed frame or sheets, just a mattress on the floor. One morning as I was lying on this mattress I had an idea that made me sit up like a capital L. What if we ran the software on the server, and let users control it by clicking on links? Then we'd never have to write anything to run on users' computers. We could generate the sites on the same server we'd serve them from. Users wouldn't need anything more than a browser. + +This kind of software, known as a web app, is common now, but at the time it wasn't clear that it was even possible. To find out, we decided to try making a version of our store builder that you could control through the browser. A couple days later, on August 12, we had one that worked. The UI was horrible, but it proved you could build a whole store through the browser, without any client software or typing anything into the command line on the server. + +Now we felt like we were really onto something. I had visions of a whole new generation of software working this way. You wouldn't need versions, or ports, or any of that crap. At Interleaf there had been a whole group called Release Engineering that seemed to be at least as big as the group that actually wrote the software. Now you could just update the software right on the server. + +We started a new company we called Viaweb, after the fact that our software worked via the web, and we got $10,000 in seed funding from Idelle's husband Julian. In return for that and doing the initial legal work and giving us business advice, we gave him 10% of the company. Ten years later this deal became the model for Y Combinator's. We knew founders needed something like this, because we'd needed it ourselves. + +At this stage I had a negative net worth, because the thousand dollars or so I had in the bank was more than counterbalanced by what I owed the government in taxes. (Had I diligently set aside the proper proportion of the money I'd made consulting for Interleaf? No, I had not.) So although Robert had his graduate student stipend, I needed that seed funding to live on. + +We originally hoped to launch in September, but we got more ambitious about the software as we worked on it. Eventually we managed to build a WYSIWYG site builder, in the sense that as you were creating pages, they looked exactly like the static ones that would be generated later, except that instead of leading to static pages, the links all referred to closures stored in a hash table on the server. + +It helped to have studied art, because the main goal of an online store builder is to make users look legit, and the key to looking legit is high production values. If you get page layouts and fonts and colors right, you can make a guy running a store out of his bedroom look more legit than a big company. + +(If you're curious why my site looks so old-fashioned, it's because it's still made with this software. It may look clunky today, but in 1996 it was the last word in slick.) + +In September, Robert rebelled. "We've been working on this for a month," he said, "and it's still not done." This is funny in retrospect, because he would still be working on it almost 3 years later. But I decided it might be prudent to recruit more programmers, and I asked Robert who else in grad school with him was really good. He recommended Trevor Blackwell, which surprised me at first, because at that point I knew Trevor mainly for his plan to reduce everything in his life to a stack of notecards, which he carried around with him. But Rtm was right, as usual. Trevor turned out to be a frighteningly effective hacker. + +It was a lot of fun working with Robert and Trevor. They're the two most independent-minded people I know, and in completely different ways. If you could see inside Rtm's brain it would look like a colonial New England church, and if you could see inside Trevor's it would look like the worst excesses of Austrian Rococo. + +We opened for business, with 6 stores, in January 1996. It was just as well we waited a few months, because although we worried we were late, we were actually almost fatally early. There was a lot of talk in the press then about ecommerce, but not many people actually wanted online stores. [8] + +There were three main parts to the software: the editor, which people used to build sites and which I wrote, the shopping cart, which Robert wrote, and the manager, which kept track of orders and statistics, and which Trevor wrote. In its time, the editor was one of the best general-purpose site builders. I kept the code tight and didn't have to integrate with any other software except Robert's and Trevor's, so it was quite fun to work on. If all I'd had to do was work on this software, the next 3 years would have been the easiest of my life. Unfortunately I had to do a lot more, all of it stuff I was worse at than programming, and the next 3 years were instead the most stressful. + +There were a lot of startups making ecommerce software in the second half of the 90s. We were determined to be the Microsoft Word, not the Interleaf. Which meant being easy to use and inexpensive. It was lucky for us that we were poor, because that caused us to make Viaweb even more inexpensive than we realized. We charged $100 a month for a small store and $300 a month for a big one. This low price was a big attraction, and a constant thorn in the sides of competitors, but it wasn't because of some clever insight that we set the price low. We had no idea what businesses paid for things. $300 a month seemed like a lot of money to us. + +We did a lot of things right by accident like that. For example, we did what's now called "doing things that don't scale," although at the time we would have described it as "being so lame that we're driven to the most desperate measures to get users." The most common of which was building stores for them. This seemed particularly humiliating, since the whole reason d'etre of our software was that people could use it to make their own stores. But anything to get users. + +We learned a lot more about retail than we wanted to know. For example, that if you could only have a small image of a man's shirt (and all images were small then by present standards), it was better to have a closeup of the collar than a picture of the whole shirt. The reason I remember learning this was that it meant I had to rescan about 30 images of men's shirts. My first set of scans were so beautiful too. + +Though this felt wrong, it was exactly the right thing to be doing. Building stores for users taught us about retail, and about how it felt to use our software. I was initially both mystified and repelled by "business" and thought we needed a "business person" to be in charge of it, but once we started to get users, I was converted, in much the same way I was converted to fatherhood once I had kids. Whatever users wanted, I was all theirs. Maybe one day we'd have so many users that I couldn't scan their images for them, but in the meantime there was nothing more important to do. + +Another thing I didn't get at the time is that growth rate is the ultimate test of a startup. Our growth rate was fine. We had about 70 stores at the end of 1996 and about 500 at the end of 1997. I mistakenly thought the thing that mattered was the absolute number of users. And that is the thing that matters in the sense that that's how much money you're making, and if you're not making enough, you might go out of business. But in the long term the growth rate takes care of the absolute number. If we'd been a startup I was advising at Y Combinator, I would have said: Stop being so stressed out, because you're doing fine. You're growing 7x a year. Just don't hire too many more people and you'll soon be profitable, and then you'll control your own destiny. + +Alas I hired lots more people, partly because our investors wanted me to, and partly because that's what startups did during the Internet Bubble. A company with just a handful of employees would have seemed amateurish. So we didn't reach breakeven until about when Yahoo bought us in the summer of 1998. Which in turn meant we were at the mercy of investors for the entire life of the company. And since both we and our investors were noobs at startups, the result was a mess even by startup standards. + +It was a huge relief when Yahoo bought us. In principle our Viaweb stock was valuable. It was a share in a business that was profitable and growing rapidly. But it didn't feel very valuable to me; I had no idea how to value a business, but I was all too keenly aware of the near-death experiences we seemed to have every few months. Nor had I changed my grad student lifestyle significantly since we started. So when Yahoo bought us it felt like going from rags to riches. Since we were going to California, I bought a car, a yellow 1998 VW GTI. I remember thinking that its leather seats alone were by far the most luxurious thing I owned. + +The next year, from the summer of 1998 to the summer of 1999, must have been the least productive of my life. I didn't realize it at the time, but I was worn out from the effort and stress of running Viaweb. For a while after I got to California I tried to continue my usual m.o. of programming till 3 in the morning, but fatigue combined with Yahoo's prematurely aged culture and grim cube farm in Santa Clara gradually dragged me down. After a few months it felt disconcertingly like working at Interleaf. + +Yahoo had given us a lot of options when they bought us. At the time I thought Yahoo was so overvalued that they'd never be worth anything, but to my astonishment the stock went up 5x in the next year. I hung on till the first chunk of options vested, then in the summer of 1999 I left. It had been so long since I'd painted anything that I'd half forgotten why I was doing this. My brain had been entirely full of software and men's shirts for 4 years. But I had done this to get rich so I could paint, I reminded myself, and now I was rich, so I should go paint. + +When I said I was leaving, my boss at Yahoo had a long conversation with me about my plans. I told him all about the kinds of pictures I wanted to paint. At the time I was touched that he took such an interest in me. Now I realize it was because he thought I was lying. My options at that point were worth about $2 million a month. If I was leaving that kind of money on the table, it could only be to go and start some new startup, and if I did, I might take people with me. This was the height of the Internet Bubble, and Yahoo was ground zero of it. My boss was at that moment a billionaire. Leaving then to start a new startup must have seemed to him an insanely, and yet also plausibly, ambitious plan. + +But I really was quitting to paint, and I started immediately. There was no time to lose. I'd already burned 4 years getting rich. Now when I talk to founders who are leaving after selling their companies, my advice is always the same: take a vacation. That's what I should have done, just gone off somewhere and done nothing for a month or two, but the idea never occurred to me. + +So I tried to paint, but I just didn't seem to have any energy or ambition. Part of the problem was that I didn't know many people in California. I'd compounded this problem by buying a house up in the Santa Cruz Mountains, with a beautiful view but miles from anywhere. I stuck it out for a few more months, then in desperation I went back to New York, where unless you understand about rent control you'll be surprised to hear I still had my apartment, sealed up like a tomb of my old life. Idelle was in New York at least, and there were other people trying to paint there, even though I didn't know any of them. + +When I got back to New York I resumed my old life, except now I was rich. It was as weird as it sounds. I resumed all my old patterns, except now there were doors where there hadn't been. Now when I was tired of walking, all I had to do was raise my hand, and (unless it was raining) a taxi would stop to pick me up. Now when I walked past charming little restaurants I could go in and order lunch. It was exciting for a while. Painting started to go better. I experimented with a new kind of still life where I'd paint one painting in the old way, then photograph it and print it, blown up, on canvas, and then use that as the underpainting for a second still life, painted from the same objects (which hopefully hadn't rotted yet). + +Meanwhile I looked for an apartment to buy. Now I could actually choose what neighborhood to live in. Where, I asked myself and various real estate agents, is the Cambridge of New York? Aided by occasional visits to actual Cambridge, I gradually realized there wasn't one. Huh. + +Around this time, in the spring of 2000, I had an idea. It was clear from our experience with Viaweb that web apps were the future. Why not build a web app for making web apps? Why not let people edit code on our server through the browser, and then host the resulting applications for them? [9] You could run all sorts of services on the servers that these applications could use just by making an API call: making and receiving phone calls, manipulating images, taking credit card payments, etc. + +I got so excited about this idea that I couldn't think about anything else. It seemed obvious that this was the future. I didn't particularly want to start another company, but it was clear that this idea would have to be embodied as one, so I decided to move to Cambridge and start it. I hoped to lure Robert into working on it with me, but there I ran into a hitch. Robert was now a postdoc at MIT, and though he'd made a lot of money the last time I'd lured him into working on one of my schemes, it had also been a huge time sink. So while he agreed that it sounded like a plausible idea, he firmly refused to work on it. + +Hmph. Well, I'd do it myself then. I recruited Dan Giffin, who had worked for Viaweb, and two undergrads who wanted summer jobs, and we got to work trying to build what it's now clear is about twenty companies and several open-source projects worth of software. The language for defining applications would of course be a dialect of Lisp. But I wasn't so naive as to assume I could spring an overt Lisp on a general audience; we'd hide the parentheses, like Dylan did. + +By then there was a name for the kind of company Viaweb was, an "application service provider," or ASP. This name didn't last long before it was replaced by "software as a service," but it was current for long enough that I named this new company after it: it was going to be called Aspra. + +I started working on the application builder, Dan worked on network infrastructure, and the two undergrads worked on the first two services (images and phone calls). But about halfway through the summer I realized I really didn't want to run a company — especially not a big one, which it was looking like this would have to be. I'd only started Viaweb because I needed the money. Now that I didn't need money anymore, why was I doing this? If this vision had to be realized as a company, then screw the vision. I'd build a subset that could be done as an open-source project. + +Much to my surprise, the time I spent working on this stuff was not wasted after all. After we started Y Combinator, I would often encounter startups working on parts of this new architecture, and it was very useful to have spent so much time thinking about it and even trying to write some of it. + +The subset I would build as an open-source project was the new Lisp, whose parentheses I now wouldn't even have to hide. A lot of Lisp hackers dream of building a new Lisp, partly because one of the distinctive features of the language is that it has dialects, and partly, I think, because we have in our minds a Platonic form of Lisp that all existing dialects fall short of. I certainly did. So at the end of the summer Dan and I switched to working on this new dialect of Lisp, which I called Arc, in a house I bought in Cambridge. + +The following spring, lightning struck. I was invited to give a talk at a Lisp conference, so I gave one about how we'd used Lisp at Viaweb. Afterward I put a postscript file of this talk online, on paulgraham.com, which I'd created years before using Viaweb but had never used for anything. In one day it got 30,000 page views. What on earth had happened? The referring urls showed that someone had posted it on Slashdot. [10] + +Wow, I thought, there's an audience. If I write something and put it on the web, anyone can read it. That may seem obvious now, but it was surprising then. In the print era there was a narrow channel to readers, guarded by fierce monsters known as editors. The only way to get an audience for anything you wrote was to get it published as a book, or in a newspaper or magazine. Now anyone could publish anything. + +This had been possible in principle since 1993, but not many people had realized it yet. I had been intimately involved with building the infrastructure of the web for most of that time, and a writer as well, and it had taken me 8 years to realize it. Even then it took me several years to understand the implications. It meant there would be a whole new generation of essays. [11] + +In the print era, the channel for publishing essays had been vanishingly small. Except for a few officially anointed thinkers who went to the right parties in New York, the only people allowed to publish essays were specialists writing about their specialties. There were so many essays that had never been written, because there had been no way to publish them. Now they could be, and I was going to write them. [12] + +I've worked on several different things, but to the extent there was a turning point where I figured out what to work on, it was when I started publishing essays online. From then on I knew that whatever else I did, I'd always write essays too. + +I knew that online essays would be a marginal medium at first. Socially they'd seem more like rants posted by nutjobs on their GeoCities sites than the genteel and beautifully typeset compositions published in The New Yorker. But by this point I knew enough to find that encouraging instead of discouraging. + +One of the most conspicuous patterns I've noticed in my life is how well it has worked, for me at least, to work on things that weren't prestigious. Still life has always been the least prestigious form of painting. Viaweb and Y Combinator both seemed lame when we started them. I still get the glassy eye from strangers when they ask what I'm writing, and I explain that it's an essay I'm going to publish on my web site. Even Lisp, though prestigious intellectually in something like the way Latin is, also seems about as hip. + +It's not that unprestigious types of work are good per se. But when you find yourself drawn to some kind of work despite its current lack of prestige, it's a sign both that there's something real to be discovered there, and that you have the right kind of motives. Impure motives are a big danger for the ambitious. If anything is going to lead you astray, it will be the desire to impress people. So while working on things that aren't prestigious doesn't guarantee you're on the right track, it at least guarantees you're not on the most common type of wrong one. + +Over the next several years I wrote lots of essays about all kinds of different topics. O'Reilly reprinted a collection of them as a book, called Hackers & Painters after one of the essays in it. I also worked on spam filters, and did some more painting. I used to have dinners for a group of friends every thursday night, which taught me how to cook for groups. And I bought another building in Cambridge, a former candy factory (and later, twas said, porn studio), to use as an office. + +One night in October 2003 there was a big party at my house. It was a clever idea of my friend Maria Daniels, who was one of the thursday diners. Three separate hosts would all invite their friends to one party. So for every guest, two thirds of the other guests would be people they didn't know but would probably like. One of the guests was someone I didn't know but would turn out to like a lot: a woman called Jessica Livingston. A couple days later I asked her out. + +Jessica was in charge of marketing at a Boston investment bank. This bank thought it understood startups, but over the next year, as she met friends of mine from the startup world, she was surprised how different reality was. And how colorful their stories were. So she decided to compile a book of interviews with startup founders. + +When the bank had financial problems and she had to fire half her staff, she started looking for a new job. In early 2005 she interviewed for a marketing job at a Boston VC firm. It took them weeks to make up their minds, and during this time I started telling her about all the things that needed to be fixed about venture capital. They should make a larger number of smaller investments instead of a handful of giant ones, they should be funding younger, more technical founders instead of MBAs, they should let the founders remain as CEO, and so on. + +One of my tricks for writing essays had always been to give talks. The prospect of having to stand up in front of a group of people and tell them something that won't waste their time is a great spur to the imagination. When the Harvard Computer Society, the undergrad computer club, asked me to give a talk, I decided I would tell them how to start a startup. Maybe they'd be able to avoid the worst of the mistakes we'd made. + +So I gave this talk, in the course of which I told them that the best sources of seed funding were successful startup founders, because then they'd be sources of advice too. Whereupon it seemed they were all looking expectantly at me. Horrified at the prospect of having my inbox flooded by business plans (if I'd only known), I blurted out "But not me!" and went on with the talk. But afterward it occurred to me that I should really stop procrastinating about angel investing. I'd been meaning to since Yahoo bought us, and now it was 7 years later and I still hadn't done one angel investment. + +Meanwhile I had been scheming with Robert and Trevor about projects we could work on together. I missed working with them, and it seemed like there had to be something we could collaborate on. + +As Jessica and I were walking home from dinner on March 11, at the corner of Garden and Walker streets, these three threads converged. Screw the VCs who were taking so long to make up their minds. We'd start our own investment firm and actually implement the ideas we'd been talking about. I'd fund it, and Jessica could quit her job and work for it, and we'd get Robert and Trevor as partners too. [13] + +Once again, ignorance worked in our favor. We had no idea how to be angel investors, and in Boston in 2005 there were no Ron Conways to learn from. So we just made what seemed like the obvious choices, and some of the things we did turned out to be novel. + +There are multiple components to Y Combinator, and we didn't figure them all out at once. The part we got first was to be an angel firm. In those days, those two words didn't go together. There were VC firms, which were organized companies with people whose job it was to make investments, but they only did big, million dollar investments. And there were angels, who did smaller investments, but these were individuals who were usually focused on other things and made investments on the side. And neither of them helped founders enough in the beginning. We knew how helpless founders were in some respects, because we remembered how helpless we'd been. For example, one thing Julian had done for us that seemed to us like magic was to get us set up as a company. We were fine writing fairly difficult software, but actually getting incorporated, with bylaws and stock and all that stuff, how on earth did you do that? Our plan was not only to make seed investments, but to do for startups everything Julian had done for us. + +YC was not organized as a fund. It was cheap enough to run that we funded it with our own money. That went right by 99% of readers, but professional investors are thinking "Wow, that means they got all the returns." But once again, this was not due to any particular insight on our part. We didn't know how VC firms were organized. It never occurred to us to try to raise a fund, and if it had, we wouldn't have known where to start. [14] + +The most distinctive thing about YC is the batch model: to fund a bunch of startups all at once, twice a year, and then to spend three months focusing intensively on trying to help them. That part we discovered by accident, not merely implicitly but explicitly due to our ignorance about investing. We needed to get experience as investors. What better way, we thought, than to fund a whole bunch of startups at once? We knew undergrads got temporary jobs at tech companies during the summer. Why not organize a summer program where they'd start startups instead? We wouldn't feel guilty for being in a sense fake investors, because they would in a similar sense be fake founders. So while we probably wouldn't make much money out of it, we'd at least get to practice being investors on them, and they for their part would probably have a more interesting summer than they would working at Microsoft. + +We'd use the building I owned in Cambridge as our headquarters. We'd all have dinner there once a week — on tuesdays, since I was already cooking for the thursday diners on thursdays — and after dinner we'd bring in experts on startups to give talks. + +We knew undergrads were deciding then about summer jobs, so in a matter of days we cooked up something we called the Summer Founders Program, and I posted an announcement on my site, inviting undergrads to apply. I had never imagined that writing essays would be a way to get "deal flow," as investors call it, but it turned out to be the perfect source. [15] We got 225 applications for the Summer Founders Program, and we were surprised to find that a lot of them were from people who'd already graduated, or were about to that spring. Already this SFP thing was starting to feel more serious than we'd intended. + +We invited about 20 of the 225 groups to interview in person, and from those we picked 8 to fund. They were an impressive group. That first batch included reddit, Justin Kan and Emmett Shear, who went on to found Twitch, Aaron Swartz, who had already helped write the RSS spec and would a few years later become a martyr for open access, and Sam Altman, who would later become the second president of YC. I don't think it was entirely luck that the first batch was so good. You had to be pretty bold to sign up for a weird thing like the Summer Founders Program instead of a summer job at a legit place like Microsoft or Goldman Sachs. + +The deal for startups was based on a combination of the deal we did with Julian ($10k for 10%) and what Robert said MIT grad students got for the summer ($6k). We invested $6k per founder, which in the typical two-founder case was $12k, in return for 6%. That had to be fair, because it was twice as good as the deal we ourselves had taken. Plus that first summer, which was really hot, Jessica brought the founders free air conditioners. [16] + +Fairly quickly I realized that we had stumbled upon the way to scale startup funding. Funding startups in batches was more convenient for us, because it meant we could do things for a lot of startups at once, but being part of a batch was better for the startups too. It solved one of the biggest problems faced by founders: the isolation. Now you not only had colleagues, but colleagues who understood the problems you were facing and could tell you how they were solving them. + +As YC grew, we started to notice other advantages of scale. The alumni became a tight community, dedicated to helping one another, and especially the current batch, whose shoes they remembered being in. We also noticed that the startups were becoming one another's customers. We used to refer jokingly to the "YC GDP," but as YC grows this becomes less and less of a joke. Now lots of startups get their initial set of customers almost entirely from among their batchmates. + +I had not originally intended YC to be a full-time job. I was going to do three things: hack, write essays, and work on YC. As YC grew, and I grew more excited about it, it started to take up a lot more than a third of my attention. But for the first few years I was still able to work on other things. + +In the summer of 2006, Robert and I started working on a new version of Arc. This one was reasonably fast, because it was compiled into Scheme. To test this new Arc, I wrote Hacker News in it. It was originally meant to be a news aggregator for startup founders and was called Startup News, but after a few months I got tired of reading about nothing but startups. Plus it wasn't startup founders we wanted to reach. It was future startup founders. So I changed the name to Hacker News and the topic to whatever engaged one's intellectual curiosity. + +HN was no doubt good for YC, but it was also by far the biggest source of stress for me. If all I'd had to do was select and help founders, life would have been so easy. And that implies that HN was a mistake. Surely the biggest source of stress in one's work should at least be something close to the core of the work. Whereas I was like someone who was in pain while running a marathon not from the exertion of running, but because I had a blister from an ill-fitting shoe. When I was dealing with some urgent problem during YC, there was about a 60% chance it had to do with HN, and a 40% chance it had do with everything else combined. [17] + +As well as HN, I wrote all of YC's internal software in Arc. But while I continued to work a good deal in Arc, I gradually stopped working on Arc, partly because I didn't have time to, and partly because it was a lot less attractive to mess around with the language now that we had all this infrastructure depending on it. So now my three projects were reduced to two: writing essays and working on YC. + +YC was different from other kinds of work I've done. Instead of deciding for myself what to work on, the problems came to me. Every 6 months there was a new batch of startups, and their problems, whatever they were, became our problems. It was very engaging work, because their problems were quite varied, and the good founders were very effective. If you were trying to learn the most you could about startups in the shortest possible time, you couldn't have picked a better way to do it. + +There were parts of the job I didn't like. Disputes between cofounders, figuring out when people were lying to us, fighting with people who maltreated the startups, and so on. But I worked hard even at the parts I didn't like. I was haunted by something Kevin Hale once said about companies: "No one works harder than the boss." He meant it both descriptively and prescriptively, and it was the second part that scared me. I wanted YC to be good, so if how hard I worked set the upper bound on how hard everyone else worked, I'd better work very hard. + +One day in 2010, when he was visiting California for interviews, Robert Morris did something astonishing: he offered me unsolicited advice. I can only remember him doing that once before. One day at Viaweb, when I was bent over double from a kidney stone, he suggested that it would be a good idea for him to take me to the hospital. That was what it took for Rtm to offer unsolicited advice. So I remember his exact words very clearly. "You know," he said, "you should make sure Y Combinator isn't the last cool thing you do." + +At the time I didn't understand what he meant, but gradually it dawned on me that he was saying I should quit. This seemed strange advice, because YC was doing great. But if there was one thing rarer than Rtm offering advice, it was Rtm being wrong. So this set me thinking. It was true that on my current trajectory, YC would be the last thing I did, because it was only taking up more of my attention. It had already eaten Arc, and was in the process of eating essays too. Either YC was my life's work or I'd have to leave eventually. And it wasn't, so I would. + +In the summer of 2012 my mother had a stroke, and the cause turned out to be a blood clot caused by colon cancer. The stroke destroyed her balance, and she was put in a nursing home, but she really wanted to get out of it and back to her house, and my sister and I were determined to help her do it. I used to fly up to Oregon to visit her regularly, and I had a lot of time to think on those flights. On one of them I realized I was ready to hand YC over to someone else. + +I asked Jessica if she wanted to be president, but she didn't, so we decided we'd try to recruit Sam Altman. We talked to Robert and Trevor and we agreed to make it a complete changing of the guard. Up till that point YC had been controlled by the original LLC we four had started. But we wanted YC to last for a long time, and to do that it couldn't be controlled by the founders. So if Sam said yes, we'd let him reorganize YC. Robert and I would retire, and Jessica and Trevor would become ordinary partners. + +When we asked Sam if he wanted to be president of YC, initially he said no. He wanted to start a startup to make nuclear reactors. But I kept at it, and in October 2013 he finally agreed. We decided he'd take over starting with the winter 2014 batch. For the rest of 2013 I left running YC more and more to Sam, partly so he could learn the job, and partly because I was focused on my mother, whose cancer had returned. + +She died on January 15, 2014. We knew this was coming, but it was still hard when it did. + +I kept working on YC till March, to help get that batch of startups through Demo Day, then I checked out pretty completely. (I still talk to alumni and to new startups working on things I'm interested in, but that only takes a few hours a week.) + +What should I do next? Rtm's advice hadn't included anything about that. I wanted to do something completely different, so I decided I'd paint. I wanted to see how good I could get if I really focused on it. So the day after I stopped working on YC, I started painting. I was rusty and it took a while to get back into shape, but it was at least completely engaging. [18] + +I spent most of the rest of 2014 painting. I'd never been able to work so uninterruptedly before, and I got to be better than I had been. Not good enough, but better. Then in November, right in the middle of a painting, I ran out of steam. Up till that point I'd always been curious to see how the painting I was working on would turn out, but suddenly finishing this one seemed like a chore. So I stopped working on it and cleaned my brushes and haven't painted since. So far anyway. + +I realize that sounds rather wimpy. But attention is a zero sum game. If you can choose what to work on, and you choose a project that's not the best one (or at least a good one) for you, then it's getting in the way of another project that is. And at 50 there was some opportunity cost to screwing around. + +I started writing essays again, and wrote a bunch of new ones over the next few months. I even wrote a couple that weren't about startups. Then in March 2015 I started working on Lisp again. + +The distinctive thing about Lisp is that its core is a language defined by writing an interpreter in itself. It wasn't originally intended as a programming language in the ordinary sense. It was meant to be a formal model of computation, an alternative to the Turing machine. If you want to write an interpreter for a language in itself, what's the minimum set of predefined operators you need? The Lisp that John McCarthy invented, or more accurately discovered, is an answer to that question. [19] + +McCarthy didn't realize this Lisp could even be used to program computers till his grad student Steve Russell suggested it. Russell translated McCarthy's interpreter into IBM 704 machine language, and from that point Lisp started also to be a programming language in the ordinary sense. But its origins as a model of computation gave it a power and elegance that other languages couldn't match. It was this that attracted me in college, though I didn't understand why at the time. + +McCarthy's 1960 Lisp did nothing more than interpret Lisp expressions. It was missing a lot of things you'd want in a programming language. So these had to be added, and when they were, they weren't defined using McCarthy's original axiomatic approach. That wouldn't have been feasible at the time. McCarthy tested his interpreter by hand-simulating the execution of programs. But it was already getting close to the limit of interpreters you could test that way — indeed, there was a bug in it that McCarthy had overlooked. To test a more complicated interpreter, you'd have had to run it, and computers then weren't powerful enough. + +Now they are, though. Now you could continue using McCarthy's axiomatic approach till you'd defined a complete programming language. And as long as every change you made to McCarthy's Lisp was a discoveredness-preserving transformation, you could, in principle, end up with a complete language that had this quality. Harder to do than to talk about, of course, but if it was possible in principle, why not try? So I decided to take a shot at it. It took 4 years, from March 26, 2015 to October 12, 2019. It was fortunate that I had a precisely defined goal, or it would have been hard to keep at it for so long. + +I wrote this new Lisp, called Bel, in itself in Arc. That may sound like a contradiction, but it's an indication of the sort of trickery I had to engage in to make this work. By means of an egregious collection of hacks I managed to make something close enough to an interpreter written in itself that could actually run. Not fast, but fast enough to test. + +I had to ban myself from writing essays during most of this time, or I'd never have finished. In late 2015 I spent 3 months writing essays, and when I went back to working on Bel I could barely understand the code. Not so much because it was badly written as because the problem is so convoluted. When you're working on an interpreter written in itself, it's hard to keep track of what's happening at what level, and errors can be practically encrypted by the time you get them. + +So I said no more essays till Bel was done. But I told few people about Bel while I was working on it. So for years it must have seemed that I was doing nothing, when in fact I was working harder than I'd ever worked on anything. Occasionally after wrestling for hours with some gruesome bug I'd check Twitter or HN and see someone asking "Does Paul Graham still code?" + +Working on Bel was hard but satisfying. I worked on it so intensively that at any given time I had a decent chunk of the code in my head and could write more there. I remember taking the boys to the coast on a sunny day in 2015 and figuring out how to deal with some problem involving continuations while I watched them play in the tide pools. It felt like I was doing life right. I remember that because I was slightly dismayed at how novel it felt. The good news is that I had more moments like this over the next few years. + +In the summer of 2016 we moved to England. We wanted our kids to see what it was like living in another country, and since I was a British citizen by birth, that seemed the obvious choice. We only meant to stay for a year, but we liked it so much that we still live there. So most of Bel was written in England. + +In the fall of 2019, Bel was finally finished. Like McCarthy's original Lisp, it's a spec rather than an implementation, although like McCarthy's Lisp it's a spec expressed as code. + +Now that I could write essays again, I wrote a bunch about topics I'd had stacked up. I kept writing essays through 2020, but I also started to think about other things I could work on. How should I choose what to do? Well, how had I chosen what to work on in the past? I wrote an essay for myself to answer that question, and I was surprised how long and messy the answer turned out to be. If this surprised me, who'd lived it, then I thought perhaps it would be interesting to other people, and encouraging to those with similarly messy lives. So I wrote a more detailed version for others to read, and this is the last sentence of it. + + + + + + + + + +Notes + +[1] My experience skipped a step in the evolution of computers: time-sharing machines with interactive OSes. I went straight from batch processing to microcomputers, which made microcomputers seem all the more exciting. + +[2] Italian words for abstract concepts can nearly always be predicted from their English cognates (except for occasional traps like polluzione). It's the everyday words that differ. So if you string together a lot of abstract concepts with a few simple verbs, you can make a little Italian go a long way. + +[3] I lived at Piazza San Felice 4, so my walk to the Accademia went straight down the spine of old Florence: past the Pitti, across the bridge, past Orsanmichele, between the Duomo and the Baptistery, and then up Via Ricasoli to Piazza San Marco. I saw Florence at street level in every possible condition, from empty dark winter evenings to sweltering summer days when the streets were packed with tourists. + +[4] You can of course paint people like still lives if you want to, and they're willing. That sort of portrait is arguably the apex of still life painting, though the long sitting does tend to produce pained expressions in the sitters. + +[5] Interleaf was one of many companies that had smart people and built impressive technology, and yet got crushed by Moore's Law. In the 1990s the exponential growth in the power of commodity (i.e. Intel) processors rolled up high-end, special-purpose hardware and software companies like a bulldozer. + +[6] The signature style seekers at RISD weren't specifically mercenary. In the art world, money and coolness are tightly coupled. Anything expensive comes to be seen as cool, and anything seen as cool will soon become equally expensive. + +[7] Technically the apartment wasn't rent-controlled but rent-stabilized, but this is a refinement only New Yorkers would know or care about. The point is that it was really cheap, less than half market price. + +[8] Most software you can launch as soon as it's done. But when the software is an online store builder and you're hosting the stores, if you don't have any users yet, that fact will be painfully obvious. So before we could launch publicly we had to launch privately, in the sense of recruiting an initial set of users and making sure they had decent-looking stores. + +[9] We'd had a code editor in Viaweb for users to define their own page styles. They didn't know it, but they were editing Lisp expressions underneath. But this wasn't an app editor, because the code ran when the merchants' sites were generated, not when shoppers visited them. + +[10] This was the first instance of what is now a familiar experience, and so was what happened next, when I read the comments and found they were full of angry people. How could I claim that Lisp was better than other languages? Weren't they all Turing complete? People who see the responses to essays I write sometimes tell me how sorry they feel for me, but I'm not exaggerating when I reply that it has always been like this, since the very beginning. It comes with the territory. An essay must tell readers things they don't already know, and some people dislike being told such things. + +[11] People put plenty of stuff on the internet in the 90s of course, but putting something online is not the same as publishing it online. Publishing online means you treat the online version as the (or at least a) primary version. + +[12] There is a general lesson here that our experience with Y Combinator also teaches: Customs continue to constrain you long after the restrictions that caused them have disappeared. Customary VC practice had once, like the customs about publishing essays, been based on real constraints. Startups had once been much more expensive to start, and proportionally rare. Now they could be cheap and common, but the VCs' customs still reflected the old world, just as customs about writing essays still reflected the constraints of the print era. + +Which in turn implies that people who are independent-minded (i.e. less influenced by custom) will have an advantage in fields affected by rapid change (where customs are more likely to be obsolete). + +Here's an interesting point, though: you can't always predict which fields will be affected by rapid change. Obviously software and venture capital will be, but who would have predicted that essay writing would be? + +[13] Y Combinator was not the original name. At first we were called Cambridge Seed. But we didn't want a regional name, in case someone copied us in Silicon Valley, so we renamed ourselves after one of the coolest tricks in the lambda calculus, the Y combinator. + +I picked orange as our color partly because it's the warmest, and partly because no VC used it. In 2005 all the VCs used staid colors like maroon, navy blue, and forest green, because they were trying to appeal to LPs, not founders. The YC logo itself is an inside joke: the Viaweb logo had been a white V on a red circle, so I made the YC logo a white Y on an orange square. + +[14] YC did become a fund for a couple years starting in 2009, because it was getting so big I could no longer afford to fund it personally. But after Heroku got bought we had enough money to go back to being self-funded. + +[15] I've never liked the term "deal flow," because it implies that the number of new startups at any given time is fixed. This is not only false, but it's the purpose of YC to falsify it, by causing startups to be founded that would not otherwise have existed. + +[16] She reports that they were all different shapes and sizes, because there was a run on air conditioners and she had to get whatever she could, but that they were all heavier than she could carry now. + +[17] Another problem with HN was a bizarre edge case that occurs when you both write essays and run a forum. When you run a forum, you're assumed to see if not every conversation, at least every conversation involving you. And when you write essays, people post highly imaginative misinterpretations of them on forums. Individually these two phenomena are tedious but bearable, but the combination is disastrous. You actually have to respond to the misinterpretations, because the assumption that you're present in the conversation means that not responding to any sufficiently upvoted misinterpretation reads as a tacit admission that it's correct. But that in turn encourages more; anyone who wants to pick a fight with you senses that now is their chance. + +[18] The worst thing about leaving YC was not working with Jessica anymore. We'd been working on YC almost the whole time we'd known each other, and we'd neither tried nor wanted to separate it from our personal lives, so leaving was like pulling up a deeply rooted tree. + +[19] One way to get more precise about the concept of invented vs discovered is to talk about space aliens. Any sufficiently advanced alien civilization would certainly know about the Pythagorean theorem, for example. I believe, though with less certainty, that they would also know about the Lisp in McCarthy's 1960 paper. + +But if so there's no reason to suppose that this is the limit of the language that might be known to them. Presumably aliens need numbers and errors and I/O too. So it seems likely there exists at least one path out of McCarthy's Lisp along which discoveredness is preserved. + + + +Thanks to Trevor Blackwell, John Collison, Patrick Collison, Daniel Gackle, Ralph Hazell, Jessica Livingston, Robert Morris, and Harj Taggar for reading drafts of this. From 50959abf0c44886721e171ecb8f1fd6091005b43 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 14:12:00 -0800 Subject: [PATCH 097/215] infra: google cse id integration test (#16238) --- .github/workflows/_integration_test.yml | 1 + .github/workflows/_release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index 586ac577dfea5..fb69b0b43caba 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -52,6 +52,7 @@ jobs: TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }} + GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }} run: | make integration_tests diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 923a647bb54c8..23b65f238d70a 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -171,6 +171,7 @@ jobs: TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }} + GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }} run: make integration_tests working-directory: ${{ inputs.working-directory }} From 2f348c695ab13e74ae76b6e903c53206de1fc6e1 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 14:20:02 -0800 Subject: [PATCH 098/215] infra: add nvidia api secret to integration testing (#15972) --- .github/workflows/_integration_test.yml | 1 + .github/workflows/_release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index fb69b0b43caba..717d137e19838 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -51,6 +51,7 @@ jobs: MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }} GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }} GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }} run: | diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 23b65f238d70a..b173e559aed09 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -170,6 +170,7 @@ jobs: MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }} GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }} GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }} run: make integration_tests From e5878c467a8a622f4237ac66d7c0d879562b52ad Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Thu, 18 Jan 2024 14:28:01 -0800 Subject: [PATCH 099/215] infra: scheduled testing env (#16239) --- .github/workflows/_integration_test.yml | 1 + .github/workflows/_release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index 717d137e19838..baf0ccacb05ba 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -12,6 +12,7 @@ env: jobs: build: + environment: Scheduled testing defaults: run: working-directory: ${{ inputs.working-directory }} diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index b173e559aed09..9351196fe5e97 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -21,6 +21,7 @@ env: jobs: build: if: github.ref == 'refs/heads/master' + environment: Scheduled testing runs-on: ubuntu-latest outputs: From f175bf7d7bc56bc43dc07d88c4a0d4386e2b0d3a Mon Sep 17 00:00:00 2001 From: SN <6432132+samnoyes@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:15:26 -0800 Subject: [PATCH 100/215] Use env for revision id if not passed in as param; use `git describe` as backup (#16227) Co-authored-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> --- libs/core/poetry.lock | 18 ++++------------ libs/core/pyproject.toml | 2 +- .../smith/evaluation/runner_utils.py | 6 +++++- libs/langchain/poetry.lock | 20 +++++------------- libs/langchain/pyproject.toml | 2 +- .../tests/unit_tests/load/test_dump.py | 21 +++++++++++-------- 6 files changed, 28 insertions(+), 41 deletions(-) diff --git a/libs/core/poetry.lock b/libs/core/poetry.lock index 84fa8613b5208..a800ee41841a3 100644 --- a/libs/core/poetry.lock +++ b/libs/core/poetry.lock @@ -1124,13 +1124,13 @@ files = [ [[package]] name = "langsmith" -version = "0.0.65" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.65-py3-none-any.whl", hash = "sha256:92450957d1c6b6be814f9b726f3bc751deca684535fb404508ccad7aec1bb049"}, - {file = "langsmith-0.0.65.tar.gz", hash = "sha256:ef20e2e32392fb1a0fc5d171e8de595d868b4153a10cc119d7bf8418192c06b6"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -1164,16 +1164,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2765,4 +2755,4 @@ extended-testing = ["jinja2"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "941239bad030d610728f204c17ea49ddbb6fc72e55dadf1691f317f4795c7b1f" +content-hash = "57f3d2b399d31ffc19811c50c969c44cf7c072facbb8f48bc8aa9281a3f16c1b" diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 1770fb6478e3d..993c8183c830e 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -11,7 +11,7 @@ repository = "https://github.com/langchain-ai/langchain" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" pydantic = ">=1,<3" -langsmith = "~0.0.63" +langsmith = "^0.0.83" tenacity = "^8.1.0" jsonpatch = "^1.33" anyio = ">=3,<5" diff --git a/libs/langchain/langchain/smith/evaluation/runner_utils.py b/libs/langchain/langchain/smith/evaluation/runner_utils.py index df04e0f88f610..ce50c1500af58 100644 --- a/libs/langchain/langchain/smith/evaluation/runner_utils.py +++ b/libs/langchain/langchain/smith/evaluation/runner_utils.py @@ -34,7 +34,7 @@ ) from langchain_core.tracers.langchain import LangChainTracer from langsmith.client import Client -from langsmith.env import get_git_info +from langsmith.env import get_git_info, get_langchain_env_var_metadata from langsmith.evaluation import EvaluationResult, RunEvaluator from langsmith.run_helpers import as_runnable, is_traceable_function from langsmith.schemas import Dataset, DataType, Example, TracerSession @@ -1227,6 +1227,8 @@ async def arun_on_dataset( input_mapper = kwargs.pop("input_mapper", None) if input_mapper: warn_deprecated("0.0.305", message=_INPUT_MAPPER_DEP_WARNING, pending=True) + if revision_id is None: + revision_id = get_langchain_env_var_metadata().get("revision_id") if kwargs: warn_deprecated( @@ -1281,6 +1283,8 @@ def run_on_dataset( input_mapper = kwargs.pop("input_mapper", None) if input_mapper: warn_deprecated("0.0.305", message=_INPUT_MAPPER_DEP_WARNING, pending=True) + if revision_id is None: + revision_id = get_langchain_env_var_metadata().get("revision_id") if kwargs: warn_deprecated( diff --git a/libs/langchain/poetry.lock b/libs/langchain/poetry.lock index 0840dab296c4b..294271e414d94 100644 --- a/libs/langchain/poetry.lock +++ b/libs/langchain/poetry.lock @@ -3049,7 +3049,6 @@ files = [ {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:227b178b22a7f91ae88525810441791b1ca1fc71c86f03190911793be15cec3d"}, {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:780eb6383fbae12afa819ef676fc93e1548ae4b076c004a393af26a04b460742"}, {file = "jq-1.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08ded6467f4ef89fec35b2bf310f210f8cd13fbd9d80e521500889edf8d22441"}, - {file = "jq-1.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49e44ed677713f4115bd5bf2dbae23baa4cd503be350e12a1c1f506b0687848f"}, {file = "jq-1.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:984f33862af285ad3e41e23179ac4795f1701822473e1a26bf87ff023e5a89ea"}, {file = "jq-1.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42264fafc6166efb5611b5d4cb01058887d050a6c19334f6a3f8a13bb369df5"}, {file = "jq-1.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a67154f150aaf76cc1294032ed588436eb002097dd4fd1e283824bf753a05080"}, @@ -3518,13 +3517,13 @@ tiktoken = ">=0.5.2,<0.6.0" [[package]] name = "langsmith" -version = "0.0.77" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.77-py3-none-any.whl", hash = "sha256:750c0aa9177240c64e131d831e009ed08dd59038f7cabbd0bbcf62ccb7c8dcac"}, - {file = "langsmith-0.0.77.tar.gz", hash = "sha256:c4c8d3a96ad8671a41064f3ccc673e2e22a4153e823b19f915c9c9b8a4f33a2c"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -3744,16 +3743,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -5807,6 +5796,7 @@ files = [ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96"}, {file = "pymongo-4.5.0-cp312-cp312-win32.whl", hash = "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60"}, {file = "pymongo-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807"}, + {file = "pymongo-4.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8443f3a8ab2d929efa761c6ebce39a6c1dca1c9ac186ebf11b62c8fe1aef53f4"}, {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a"}, {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257"}, {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93"}, @@ -9128,4 +9118,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "56656496974df81ce802cecd9a710bcf3bf9807b3496da4cea33a9a6e4146e86" +content-hash = "395f3b3b3c486be29ab663cdb12ce577c231027b32671c4f48848944883db9f8" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index 4fc8a74d3c012..1547b98c5ea46 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -80,7 +80,7 @@ cassio = {version = "^0.1.0", optional = true} sympy = {version = "^1.12", optional = true} rapidfuzz = {version = "^3.1.1", optional = true} jsonschema = {version = ">1", optional = true} -langsmith = "~0.0.77" +langsmith = "^0.0.83" rank-bm25 = {version = "^0.2.2", optional = true} geopandas = {version = "^0.13.1", optional = true} gitpython = {version = "^3.1.32", optional = true} diff --git a/libs/langchain/tests/unit_tests/load/test_dump.py b/libs/langchain/tests/unit_tests/load/test_dump.py index 0d0a354172c87..3e59fbda81186 100644 --- a/libs/langchain/tests/unit_tests/load/test_dump.py +++ b/libs/langchain/tests/unit_tests/load/test_dump.py @@ -1,6 +1,8 @@ """Test for Serializable base class""" +import os from typing import Any, Dict, List +from unittest.mock import patch import pytest from langchain_community.chat_models.openai import ChatOpenAI @@ -74,15 +76,16 @@ def test_typeerror() -> None: @pytest.mark.requires("openai") def test_serialize_openai_llm(snapshot: Any) -> None: - llm = OpenAI( - model="davinci", - temperature=0.5, - openai_api_key="hello", - # This is excluded from serialization - callbacks=[LangChainTracer()], - ) - llm.temperature = 0.7 # this is reflected in serialization - assert dumps(llm, pretty=True) == snapshot + with patch.dict(os.environ, {"LANGCHAIN_API_KEY": "test-api-key"}): + llm = OpenAI( + model="davinci", + temperature=0.5, + openai_api_key="hello", + # This is excluded from serialization + callbacks=[LangChainTracer()], + ) + llm.temperature = 0.7 # this is reflected in serialization + assert dumps(llm, pretty=True) == snapshot @pytest.mark.requires("openai") From 177af65dc4d0033330bd528eb7dd65d62b0d7dc3 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Thu, 18 Jan 2024 21:27:01 -0500 Subject: [PATCH 101/215] core[minor]: RFC Add astream_events to Runnables (#16172) This PR adds `astream_events` method to Runnables to make it easier to stream data from arbitrary chains. * Streaming only works properly in async right now * One should use `astream()` with if mixing in imperative code as might be done with tool implementations * Astream_log has been modified with minimal additive changes, so no breaking changes are expected * Underlying callback code / tracing code should be refactored at some point to handle things more consistently (OK for now) - ~~[ ] verify event for on_retry~~ does not work until we implement streaming for retry - ~~[ ] Any rrenaming? Should we rename "event" to "hook"?~~ - [ ] Any other feedback from community? - [x] throw NotImplementedError for `RunnableEach` for now ## Example See this [Example Notebook](https://github.com/langchain-ai/langchain/blob/dbbc7fa0d66bcdde420760baa0ddb9918c12c349/docs/docs/modules/agents/how_to/streaming_events.ipynb) for an example with streaming in the context of an Agent ## Event Hooks Reference Here is a reference table that shows some events that might be emitted by the various Runnable objects. Definitions for some of the Runnable are included after the table. | event | name | chunk | input | output | |----------------------|------------------|---------------------------------|-----------------------------------------------|-------------------------------------------------| | on_chat_model_start | [model name] | | {"messages": [[SystemMessage, HumanMessage]]} | | | on_chat_model_stream | [model name] | AIMessageChunk(content="hello") | | | | on_chat_model_end | [model name] | | {"messages": [[SystemMessage, HumanMessage]]} | {"generations": [...], "llm_output": None, ...} | | on_llm_start | [model name] | | {'input': 'hello'} | | | on_llm_stream | [model name] | 'Hello' | | | | on_llm_end | [model name] | | 'Hello human!' | | on_chain_start | format_docs | | | | | on_chain_stream | format_docs | "hello world!, goodbye world!" | | | | on_chain_end | format_docs | | [Document(...)] | "hello world!, goodbye world!" | | on_tool_start | some_tool | | {"x": 1, "y": "2"} | | | on_tool_stream | some_tool | {"x": 1, "y": "2"} | | | | on_tool_end | some_tool | | | {"x": 1, "y": "2"} | | on_retriever_start | [retriever name] | | {"query": "hello"} | | | on_retriever_chunk | [retriever name] | {documents: [...]} | | | | on_retriever_end | [retriever name] | | {"query": "hello"} | {documents: [...]} | | on_prompt_start | [template_name] | | {"question": "hello"} | | | on_prompt_end | [template_name] | | {"question": "hello"} | ChatPromptValue(messages: [SystemMessage, ...]) | Here are declarations associated with the events shown above: `format_docs`: ```python def format_docs(docs: List[Document]) -> str: '''Format the docs.''' return ", ".join([doc.page_content for doc in docs]) format_docs = RunnableLambda(format_docs) ``` `some_tool`: ```python @tool def some_tool(x: int, y: str) -> dict: '''Some_tool.''' return {"x": x, "y": y} ``` `prompt`: ```python template = ChatPromptTemplate.from_messages( [("system", "You are Cat Agent 007"), ("human", "{question}")] ).with_config({"run_name": "my_template", "tags": ["my_template"]}) ``` --- .../agents/how_to/streaming_events.ipynb | 350 ++++++ libs/core/langchain_core/callbacks/base.py | 2 + libs/core/langchain_core/callbacks/manager.py | 16 +- libs/core/langchain_core/runnables/base.py | 429 +++++-- libs/core/langchain_core/runnables/schema.py | 133 ++ libs/core/langchain_core/runnables/utils.py | 58 + libs/core/langchain_core/tools.py | 8 +- libs/core/langchain_core/tracers/base.py | 148 ++- libs/core/langchain_core/tracers/langchain.py | 3 +- .../core/langchain_core/tracers/log_stream.py | 311 ++++- .../unit_tests/runnables/test_runnable.py | 18 +- .../runnables/test_runnable_events.py | 1065 +++++++++++++++++ 12 files changed, 2425 insertions(+), 116 deletions(-) create mode 100644 docs/docs/modules/agents/how_to/streaming_events.ipynb create mode 100644 libs/core/langchain_core/runnables/schema.py create mode 100644 libs/core/tests/unit_tests/runnables/test_runnable_events.py diff --git a/docs/docs/modules/agents/how_to/streaming_events.ipynb b/docs/docs/modules/agents/how_to/streaming_events.ipynb new file mode 100644 index 0000000000000..4f1ad14a374ba --- /dev/null +++ b/docs/docs/modules/agents/how_to/streaming_events.ipynb @@ -0,0 +1,350 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b69e747b-4e79-4caf-8f8b-c6e70275a31d", + "metadata": {}, + "source": [ + "# Event Streaming\n", + "\n", + "**NEW** This is a new API only works with recent versions of langchain-core!\n", + "\n", + "In this notebook, we'll see how to use `astream_events` to stream **token by token** from LLM calls used within the tools invoked by the agent. \n", + "\n", + "We will **only** stream tokens from LLMs used within tools and from no other LLMs (just to show that we can)! \n", + "\n", + "Feel free to adapt this example to the needs of your application.\n", + "\n", + "Our agent will use the OpenAI tools API for tool invocation, and we'll provide the agent with two tools:\n", + "\n", + "1. `where_cat_is_hiding`: A tool that uses an LLM to tell us where the cat is hiding\n", + "2. `tell_me_a_joke_about`: A tool that can use an LLM to tell a joke about the given topic\n", + "\n", + "\n", + "## ⚠️ Beta API ⚠️ ##\n", + "\n", + "Event Streaming is a **beta** API, and may change a bit based on feedback.\n", + "\n", + "Keep in mind the following constraints (repeated in tools section):\n", + "\n", + "* streaming only works properly if using `async`\n", + "* propagate callbacks if definning custom functions / runnables\n", + "* If creating a tool that uses an LLM, make sure to use `.astream()` on the LLM rather than `.ainvoke` to ask the LLM to stream tokens.\n", + "\n", + "## Event Hooks Reference\n", + "\n", + "\n", + "Here is a reference table that shows some events that might be emitted by the various Runnable objects.\n", + "Definitions for some of the Runnable are included after the table.\n", + "\n", + "⚠️ When streaming the inputs for the runnable will not be available until the input stream has been entirely consumed This means that the inputs will be available at for the corresponding `end` hook rather than `start` event.\n", + "\n", + "\n", + "| event | name | chunk | input | output |\n", + "|----------------------|------------------|---------------------------------|-----------------------------------------------|-------------------------------------------------|\n", + "| on_chat_model_start | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | |\n", + "| on_chat_model_stream | [model name] | AIMessageChunk(content=\"hello\") | | |\n", + "| on_chat_model_end | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | {\"generations\": [...], \"llm_output\": None, ...} |\n", + "| on_llm_start | [model name] | | {'input': 'hello'} | |\n", + "| on_llm_stream | [model name] | 'Hello' | | |\n", + "| on_llm_end | [model name] | | 'Hello human!' |\n", + "| on_chain_start | format_docs | | | |\n", + "| on_chain_stream | format_docs | \"hello world!, goodbye world!\" | | |\n", + "| on_chain_end | format_docs | | [Document(...)] | \"hello world!, goodbye world!\" |\n", + "| on_tool_start | some_tool | | {\"x\": 1, \"y\": \"2\"} | |\n", + "| on_tool_stream | some_tool | {\"x\": 1, \"y\": \"2\"} | | |\n", + "| on_tool_end | some_tool | | | {\"x\": 1, \"y\": \"2\"} |\n", + "| on_retriever_start | [retriever name] | | {\"query\": \"hello\"} | |\n", + "| on_retriever_chunk | [retriever name] | {documents: [...]} | | |\n", + "| on_retriever_end | [retriever name] | | {\"query\": \"hello\"} | {documents: [...]} |\n", + "| on_prompt_start | [template_name] | | {\"question\": \"hello\"} | |\n", + "| on_prompt_end | [template_name] | | {\"question\": \"hello\"} | ChatPromptValue(messages: [SystemMessage, ...]) |\n", + "\n", + "\n", + "Here are declarations associated with the events shown above:\n", + "\n", + "`format_docs`:\n", + "\n", + "```python\n", + "def format_docs(docs: List[Document]) -> str:\n", + " '''Format the docs.'''\n", + " return \", \".join([doc.page_content for doc in docs])\n", + "\n", + "format_docs = RunnableLambda(format_docs)\n", + "```\n", + "\n", + "`some_tool`:\n", + "\n", + "```python\n", + "@tool\n", + "def some_tool(x: int, y: str) -> dict:\n", + " '''Some_tool.'''\n", + " return {\"x\": x, \"y\": y}\n", + "```\n", + "\n", + "`prompt`:\n", + "\n", + "```python\n", + "template = ChatPromptTemplate.from_messages(\n", + " [(\"system\", \"You are Cat Agent 007\"), (\"human\", \"{question}\")]\n", + ").with_config({\"run_name\": \"my_template\", \"tags\": [\"my_template\"]})\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "29205bef-2288-48e9-9067-f19072277a97", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_openai_tools_agent\n", + "from langchain.tools import tool\n", + "from langchain_core.callbacks import Callbacks\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_openai import ChatOpenAI" + ] + }, + { + "cell_type": "markdown", + "id": "d6b0fafa-ce3b-489b-bf1d-d37b87f4819e", + "metadata": {}, + "source": [ + "## Create the model\n", + "\n", + "**Attention** For older versions of langchain, we must set `streaming=True`" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fa3c3761-a1cd-4118-8559-ea4d8857d394", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "model = ChatOpenAI(temperature=0, streaming=True)" + ] + }, + { + "cell_type": "markdown", + "id": "b76e1a3b-2983-42d9-ac12-4a0f32cd4a24", + "metadata": {}, + "source": [ + "## Tools\n", + "\n", + "We define two tools that rely on a chat model to generate output!\n", + "\n", + "Please note a few different things:\n", + "\n", + "1. The tools are **async**\n", + "1. The model is invoked using **.astream()** to force the output to stream\n", + "1. For older langchain versions you should set `streaming=True` on the model!\n", + "1. We attach tags to the model so that we can filter on said tags in our callback handler\n", + "1. The tools accept callbacks and propagate them to the model as a runtime argument" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c767f760-fe52-47e5-9c2a-622f03507aaf", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "@tool\n", + "async def where_cat_is_hiding(callbacks: Callbacks) -> str: # <--- Accept callbacks\n", + " \"\"\"Where is the cat hiding right now?\"\"\"\n", + " chunks = [\n", + " chunk\n", + " async for chunk in model.astream(\n", + " \"Give one up to three word answer about where the cat might be hiding in the house right now.\",\n", + " {\n", + " \"tags\": [\"tool_llm\"],\n", + " \"callbacks\": callbacks,\n", + " }, # <--- Propagate callbacks and assign a tag to this model\n", + " )\n", + " ]\n", + " return \"\".join(chunk.content for chunk in chunks)\n", + "\n", + "\n", + "@tool\n", + "async def tell_me_a_joke_about(\n", + " topic: str, callbacks: Callbacks\n", + ") -> str: # <--- Accept callbacks\n", + " \"\"\"Tell a joke about a given topic.\"\"\"\n", + " template = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", \"You are Cat Agent 007. You are funny and know many jokes.\"),\n", + " (\"human\", \"Tell me a long joke about {topic}\"),\n", + " ]\n", + " )\n", + " chain = template | model.with_config({\"tags\": [\"tool_llm\"]})\n", + " chunks = [\n", + " chunk\n", + " async for chunk in chain.astream({\"topic\": topic}, {\"callbacks\": callbacks})\n", + " ]\n", + " return \"\".join(chunk.content for chunk in chunks)" + ] + }, + { + "cell_type": "markdown", + "id": "cba476f8-29da-4c2c-9134-186871caf7ae", + "metadata": {}, + "source": [ + "## Initialize the Agent" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0bab4488-bf4c-461f-b41e-5e60310fe0f2", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input_variables=['agent_scratchpad', 'input'] input_types={'chat_history': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]], 'agent_scratchpad': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]} messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')), MessagesPlaceholder(variable_name='chat_history', optional=True), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')), MessagesPlaceholder(variable_name='agent_scratchpad')]\n", + "[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')), MessagesPlaceholder(variable_name='chat_history', optional=True), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')), MessagesPlaceholder(variable_name='agent_scratchpad')]\n" + ] + } + ], + "source": [ + "# Get the prompt to use - you can modify this!\n", + "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", + "print(prompt)\n", + "print(prompt.messages)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "1762f4e1-402a-4bfb-af26-eb5b7b8f56bd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "tools = [tell_me_a_joke_about, where_cat_is_hiding]\n", + "agent = create_openai_tools_agent(model.with_config({\"tags\": [\"agent\"]}), tools, prompt)\n", + "executor = AgentExecutor(agent=agent, tools=tools)" + ] + }, + { + "cell_type": "markdown", + "id": "841271d7-1de1-41a9-9387-bb04368537f1", + "metadata": {}, + "source": [ + "## Stream the output\n", + "\n", + "The streamed output is shown with a `|` as the delimiter between tokens. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a5d94bd8-4a55-4527-b21a-4245a38c7c26", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/eugene/src/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: This API is in beta and may change in the future.\n", + " warn_beta(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--\n", + "Starting tool: where_cat_is_hiding with inputs: {}\n", + "\n", + "\n", + "|Under| the| bed|.||\n", + "\n", + "Ended tool: where_cat_is_hiding\n", + "--\n", + "Starting tool: tell_me_a_joke_about with inputs: {'topic': 'under the bed'}\n", + "\n", + "\n", + "|Sure|,| here|'s| a| long| joke| about| what|'s| hiding| under| the| bed|:\n", + "\n", + "|Once| upon| a| time|,| there| was| a| mis|chie|vous| little| boy| named| Tim|my|.| Tim|my| had| always| been| afraid| of| what| might| be| lurking| under| his| bed| at| night|.| Every| evening|,| he| would| ti|pt|oe| into| his| room|,| turn| off| the| lights|,| and| then| make| a| daring| leap| onto| his| bed|,| ensuring| that| nothing| could| grab| his| ankles|.\n", + "\n", + "|One| night|,| Tim|my|'s| parents| decided| to| play| a| prank| on| him|.| They| hid| a| remote|-controlled| toy| monster| under| his| bed|,| complete| with| glowing| eyes| and| a| grow|ling| sound| effect|.| As| Tim|my| settled| into| bed|,| his| parents| quietly| sn|uck| into| his| room|,| ready| to| give| him| the| scare| of| a| lifetime|.\n", + "\n", + "|Just| as| Tim|my| was| about| to| drift| off| to| sleep|,| he| heard| a| faint| grow|l| coming| from| under| his| bed|.| His| eyes| widened| with| fear|,| and| his| heart| started| racing|.| He| must|ered| up| the| courage| to| peek| under| the| bed|,| and| to| his| surprise|,| he| saw| a| pair| of| glowing| eyes| staring| back| at| him|.\n", + "\n", + "|Terr|ified|,| Tim|my| jumped| out| of| bed| and| ran| to| his| parents|,| screaming|,| \"|There|'s| a| monster| under| my| bed|!| Help|!\"\n", + "\n", + "|His| parents|,| trying| to| st|ifle| their| laughter|,| rushed| into| his| room|.| They| pretended| to| be| just| as| scared| as| Tim|my|,| and| together|,| they| brav|ely| approached| the| bed|.| Tim|my|'s| dad| grabbed| a| bro|om|stick|,| ready| to| defend| his| family| against| the| imaginary| monster|.\n", + "\n", + "|As| they| got| closer|,| the| \"|monster|\"| under| the| bed| started| to| move|.| Tim|my|'s| mom|,| unable| to| contain| her| laughter| any| longer|,| pressed| a| button| on| the| remote| control|,| causing| the| toy| monster| to| sc|urry| out| from| under| the| bed|.| Tim|my|'s| fear| quickly| turned| into| confusion|,| and| then| into| laughter| as| he| realized| it| was| all| just| a| prank|.\n", + "\n", + "|From| that| day| forward|,| Tim|my| learned| that| sometimes| the| things| we| fear| the| most| are| just| fig|ments| of| our| imagination|.| And| as| for| what|'s| hiding| under| his| bed|?| Well|,| it|'s| just| dust| b|unn|ies| and| the| occasional| missing| sock|.| Nothing| to| be| afraid| of|!\n", + "\n", + "|Remember|,| laughter| is| the| best| monster| repell|ent|!||\n", + "\n", + "Ended tool: tell_me_a_joke_about\n" + ] + } + ], + "source": [ + "async for event in executor.astream_events(\n", + " {\"input\": \"where is the cat hiding? Tell me a joke about that location?\"},\n", + " include_tags=[\"tool_llm\"],\n", + " include_types=[\"tool\"],\n", + "):\n", + " hook = event[\"event\"]\n", + " if hook == \"on_chat_model_stream\":\n", + " print(event[\"data\"][\"chunk\"].content, end=\"|\")\n", + " elif hook in {\"on_chat_model_start\", \"on_chat_model_end\"}:\n", + " print()\n", + " print()\n", + " elif hook == \"on_tool_start\":\n", + " print(\"--\")\n", + " print(\n", + " f\"Starting tool: {event['name']} with inputs: {event['data'].get('input')}\"\n", + " )\n", + " elif hook == \"on_tool_end\":\n", + " print(f\"Ended tool: {event['name']}\")\n", + " else:\n", + " pass" + ] + } + ], + "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.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/core/langchain_core/callbacks/base.py b/libs/core/langchain_core/callbacks/base.py index ed30e50ff14a4..ff5e7770c5aa5 100644 --- a/libs/core/langchain_core/callbacks/base.py +++ b/libs/core/langchain_core/callbacks/base.py @@ -219,6 +219,7 @@ def on_tool_start( parent_run_id: Optional[UUID] = None, tags: Optional[List[str]] = None, metadata: Optional[Dict[str, Any]] = None, + inputs: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Run when tool starts running.""" @@ -409,6 +410,7 @@ async def on_tool_start( parent_run_id: Optional[UUID] = None, tags: Optional[List[str]] = None, metadata: Optional[Dict[str, Any]] = None, + inputs: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> None: """Run when tool starts running.""" diff --git a/libs/core/langchain_core/callbacks/manager.py b/libs/core/langchain_core/callbacks/manager.py index b18f83c3aca97..56a8e04db05ef 100644 --- a/libs/core/langchain_core/callbacks/manager.py +++ b/libs/core/langchain_core/callbacks/manager.py @@ -1282,15 +1282,22 @@ def on_tool_start( input_str: str, run_id: Optional[UUID] = None, parent_run_id: Optional[UUID] = None, + inputs: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> CallbackManagerForToolRun: """Run when tool starts running. Args: - serialized (Dict[str, Any]): The serialized tool. - input_str (str): The input to the tool. - run_id (UUID, optional): The ID of the run. Defaults to None. - parent_run_id (UUID, optional): The ID of the parent run. Defaults to None. + serialized: Serialized representation of the tool. + input_str: The input to the tool as a string. + Non-string inputs are cast to strings. + run_id: ID for the run. Defaults to None. + parent_run_id: The ID of the parent run. Defaults to None. + inputs: The original input to the tool if provided. + Recommended for usage instead of input_str when the original + input is needed. + If provided, the inputs are expected to be formatted as a dict. + The keys will correspond to the named-arguments in the tool. Returns: CallbackManagerForToolRun: The callback manager for the tool run. @@ -1308,6 +1315,7 @@ def on_tool_start( parent_run_id=self.parent_run_id, tags=self.tags, metadata=self.metadata, + inputs=inputs, **kwargs, ) diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index 5ac7ce0dac415..f8b5bcf7c43ff 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -7,7 +7,6 @@ from abc import ABC, abstractmethod from concurrent.futures import FIRST_COMPLETED, wait from contextvars import copy_context -from copy import deepcopy from functools import wraps from itertools import groupby, tee from operator import itemgetter @@ -36,7 +35,8 @@ from typing_extensions import Literal, get_args -from langchain_core.load.dump import dumpd, dumps +from langchain_core._api import beta_decorator +from langchain_core.load.dump import dumpd from langchain_core.load.serializable import Serializable from langchain_core.pydantic_v1 import BaseConfig, BaseModel, Field, create_model from langchain_core.runnables.config import ( @@ -54,6 +54,7 @@ var_child_runnable_config, ) from langchain_core.runnables.graph import Graph +from langchain_core.runnables.schema import EventData, StreamEvent from langchain_core.runnables.utils import ( AddableDict, AnyConfigurableField, @@ -83,7 +84,11 @@ from langchain_core.runnables.fallbacks import ( RunnableWithFallbacks as RunnableWithFallbacksT, ) - from langchain_core.tracers.log_stream import RunLog, RunLogPatch + from langchain_core.tracers.log_stream import ( + LogEntry, + RunLog, + RunLogPatch, + ) from langchain_core.tracers.root_listeners import Listener @@ -600,7 +605,7 @@ def astream_log( exclude_names: Optional[Sequence[str]] = None, exclude_types: Optional[Sequence[str]] = None, exclude_tags: Optional[Sequence[str]] = None, - **kwargs: Optional[Any], + **kwargs: Any, ) -> AsyncIterator[RunLogPatch]: ... @@ -618,7 +623,7 @@ def astream_log( exclude_names: Optional[Sequence[str]] = None, exclude_types: Optional[Sequence[str]] = None, exclude_tags: Optional[Sequence[str]] = None, - **kwargs: Optional[Any], + **kwargs: Any, ) -> AsyncIterator[RunLog]: ... @@ -635,7 +640,7 @@ async def astream_log( exclude_names: Optional[Sequence[str]] = None, exclude_types: Optional[Sequence[str]] = None, exclude_tags: Optional[Sequence[str]] = None, - **kwargs: Optional[Any], + **kwargs: Any, ) -> Union[AsyncIterator[RunLogPatch], AsyncIterator[RunLog]]: """ Stream all output from a runnable, as reported to the callback system. @@ -659,16 +664,187 @@ async def astream_log( exclude_types: Exclude logs with these types. exclude_tags: Exclude logs with these tags. """ - import jsonpatch # type: ignore[import] + from langchain_core.tracers.log_stream import ( + LogStreamCallbackHandler, + _astream_log_implementation, + ) + + stream = LogStreamCallbackHandler( + auto_close=False, + include_names=include_names, + include_types=include_types, + include_tags=include_tags, + exclude_names=exclude_names, + exclude_types=exclude_types, + exclude_tags=exclude_tags, + _schema_format="original", + ) + + # Mypy isn't resolving the overloads here + # Likely an issue b/c `self` is being passed through + # and it's can't map it to Runnable[Input,Output]? + async for item in _astream_log_implementation( # type: ignore + self, + input, + config, + diff=diff, + stream=stream, + with_streamed_output_list=with_streamed_output_list, + ): + yield item + + @beta_decorator.beta(message="This API is in beta and may change in the future.") + async def astream_events( + self, + input: Any, + config: Optional[RunnableConfig] = None, + *, + include_names: Optional[Sequence[str]] = None, + include_types: Optional[Sequence[str]] = None, + include_tags: Optional[Sequence[str]] = None, + exclude_names: Optional[Sequence[str]] = None, + exclude_types: Optional[Sequence[str]] = None, + exclude_tags: Optional[Sequence[str]] = None, + **kwargs: Any, + ) -> AsyncIterator[StreamEvent]: + """Generate a stream of events. + + Use to create an iterator ove StreamEvents that provide real-time information + about the progress of the runnable, including StreamEvents from intermediate + results. + + A StreamEvent is a dictionary with the following schema: + + * ``event``: str - Event names are of the + format: on_[runnable_type]_(start|stream|end). + * ``name``: str - The name of the runnable that generated the event. + * ``run_id``: str - randomly generated ID associated with the given execution of + the runnable that emitted the event. + A child runnable that gets invoked as part of the execution of a + parent runnable is assigned its own unique ID. + * ``tags``: Optional[List[str]] - The tags of the runnable that generated + the event. + * ``metadata``: Optional[Dict[str, Any]] - The metadata of the runnable + that generated the event. + * ``data``: Dict[str, Any] + + + Below is a table that illustrates some evens that might be emitted by various + chains. Metadata fields have been omitted from the table for brevity. + Chain definitions have been included after the table. + + | event | name | chunk | input | output | + |----------------------|------------------|---------------------------------|-----------------------------------------------|-------------------------------------------------| + | on_chat_model_start | [model name] | | {"messages": [[SystemMessage, HumanMessage]]} | | + | on_chat_model_stream | [model name] | AIMessageChunk(content="hello") | | | + | on_chat_model_end | [model name] | | {"messages": [[SystemMessage, HumanMessage]]} | {"generations": [...], "llm_output": None, ...} | + | on_llm_start | [model name] | | {'input': 'hello'} | | + | on_llm_stream | [model name] | 'Hello' | | | + | on_llm_end | [model name] | | 'Hello human!' | + | on_chain_start | format_docs | | | | + | on_chain_stream | format_docs | "hello world!, goodbye world!" | | | + | on_chain_end | format_docs | | [Document(...)] | "hello world!, goodbye world!" | + | on_tool_start | some_tool | | {"x": 1, "y": "2"} | | + | on_tool_stream | some_tool | {"x": 1, "y": "2"} | | | + | on_tool_end | some_tool | | | {"x": 1, "y": "2"} | + | on_retriever_start | [retriever name] | | {"query": "hello"} | | + | on_retriever_chunk | [retriever name] | {documents: [...]} | | | + | on_retriever_end | [retriever name] | | {"query": "hello"} | {documents: [...]} | + | on_prompt_start | [template_name] | | {"question": "hello"} | | + | on_prompt_end | [template_name] | | {"question": "hello"} | ChatPromptValue(messages: [SystemMessage, ...]) | + + Here are declarations associated with the events shown above: + + `format_docs`: + + ```python + def format_docs(docs: List[Document]) -> str: + '''Format the docs.''' + return ", ".join([doc.page_content for doc in docs]) + + format_docs = RunnableLambda(format_docs) + ``` + + `some_tool`: + + ```python + @tool + def some_tool(x: int, y: str) -> dict: + '''Some_tool.''' + return {"x": x, "y": y} + ``` + + `prompt`: + + ```python + template = ChatPromptTemplate.from_messages( + [("system", "You are Cat Agent 007"), ("human", "{question}")] + ).with_config({"run_name": "my_template", "tags": ["my_template"]}) + ``` + + Example: + + .. code-block:: python + + from langchain_core.runnables import RunnableLambda + + async def reverse(s: str) -> str: + return s[::-1] + + chain = RunnableLambda(func=reverse) + + events = [event async for event in chain.astream_events("hello")] + + # will produce the following events (run_id has been omitted for brevity): + [ + { + "data": {"input": "hello"}, + "event": "on_chain_start", + "metadata": {}, + "name": "reverse", + "tags": [], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "reverse", + "tags": [], + }, + { + "data": {"output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "reverse", + "tags": [], + }, + ] + + Args: + input: The input to the runnable. + config: The config to use for the runnable. + include_names: Only include events from runnables with matching names. + include_types: Only include events from runnables with matching types. + include_tags: Only include events from runnables with matching tags. + exclude_names: Exclude events from runnables with matching names. + exclude_types: Exclude events from runnables with matching types. + exclude_tags: Exclude events from runnables with matching tags. + kwargs: Additional keyword arguments to pass to the runnable. + These will be passed to astream_log as this implementation + of astream_events is built on top of astream_log. - from langchain_core.callbacks.base import BaseCallbackManager + Returns: + An async stream of StreamEvents. + """ # noqa: E501 + from langchain_core.runnables.utils import ( + _RootEventFilter, + ) from langchain_core.tracers.log_stream import ( LogStreamCallbackHandler, RunLog, - RunLogPatch, + _astream_log_implementation, ) - # Create a stream handler that will emit Log objects stream = LogStreamCallbackHandler( auto_close=False, include_names=include_names, @@ -677,81 +853,159 @@ async def astream_log( exclude_names=exclude_names, exclude_types=exclude_types, exclude_tags=exclude_tags, + _schema_format="streaming_events", + ) + + run_log = RunLog(state=None) # type: ignore[arg-type] + encountered_start_event = False + + _root_event_filter = _RootEventFilter( + include_names=include_names, + include_types=include_types, + include_tags=include_tags, + exclude_names=exclude_names, + exclude_types=exclude_types, + exclude_tags=exclude_tags, ) - # Assign the stream handler to the config config = ensure_config(config) - callbacks = config.get("callbacks") - if callbacks is None: - config["callbacks"] = [stream] - elif isinstance(callbacks, list): - config["callbacks"] = callbacks + [stream] - elif isinstance(callbacks, BaseCallbackManager): - callbacks = callbacks.copy() - callbacks.add_handler(stream, inherit=True) - config["callbacks"] = callbacks - else: - raise ValueError( - f"Unexpected type for callbacks: {callbacks}." - "Expected None, list or AsyncCallbackManager." - ) + root_tags = config.get("tags", []) + root_metadata = config.get("metadata", {}) + root_name = config.get("run_name", self.get_name()) + + # Ignoring mypy complaint about too many different union combinations + # This arises because many of the argument types are unions + async for log in _astream_log_implementation( # type: ignore[misc] + self, + input, + config=config, + stream=stream, + diff=True, + with_streamed_output_list=True, + **kwargs, + ): + run_log = run_log + log + + if not encountered_start_event: + # Yield the start event for the root runnable. + encountered_start_event = True + state = run_log.state.copy() + + event = StreamEvent( + event=f"on_{state['type']}_start", + run_id=state["id"], + name=root_name, + tags=root_tags, + metadata=root_metadata, + data={ + "input": input, + }, + ) - # Call the runnable in streaming mode, - # add each chunk to the output stream - async def consume_astream() -> None: - try: - prev_final_output: Optional[Output] = None - final_output: Optional[Output] = None + if _root_event_filter.include_event(event, state["type"]): + yield event - async for chunk in self.astream(input, config, **kwargs): - prev_final_output = final_output - if final_output is None: - final_output = chunk + paths = { + op["path"].split("/")[2] + for op in log.ops + if op["path"].startswith("/logs/") + } + # Elements in a set should be iterated in the same order + # as they were inserted in modern python versions. + for path in paths: + data: EventData = {} + log_entry: LogEntry = run_log.state["logs"][path] + if log_entry["end_time"] is None: + if log_entry["streamed_output"]: + event_type = "stream" else: - try: - final_output = final_output + chunk # type: ignore - except TypeError: - final_output = chunk - patches: List[Dict[str, Any]] = [] - if with_streamed_output_list: - patches.append( - { - "op": "add", - "path": "/streamed_output/-", - # chunk cannot be shared between - # streamed_output and final_output - # otherwise jsonpatch.apply will - # modify both - "value": deepcopy(chunk), - } + event_type = "start" + else: + event_type = "end" + + if event_type == "start": + # Include the inputs with the start event if they are available. + # Usually they will NOT be available for components that operate + # on streams, since those components stream the input and + # don't know its final value until the end of the stream. + inputs = log_entry["inputs"] + if inputs is not None: + data["input"] = inputs + pass + + if event_type == "end": + inputs = log_entry["inputs"] + if inputs is not None: + data["input"] = inputs + + # None is a VALID output for an end event + data["output"] = log_entry["final_output"] + + if event_type == "stream": + num_chunks = len(log_entry["streamed_output"]) + if num_chunks != 1: + raise AssertionError( + f"Expected exactly one chunk of streamed output, " + f"got {num_chunks} instead. This is impossible. " + f"Encountered in: {log_entry['name']}" ) - for op in jsonpatch.JsonPatch.from_diff( - prev_final_output, final_output, dumps=dumps - ): - patches.append({**op, "path": f"/final_output{op['path']}"}) - await stream.send_stream.send(RunLogPatch(*patches)) - finally: - await stream.send_stream.aclose() - # Start the runnable in a task, so we can start consuming output - task = asyncio.create_task(consume_astream()) + data = {"chunk": log_entry["streamed_output"][0]} + # Clean up the stream, we don't need it anymore. + # And this avoids duplicates as well! + log_entry["streamed_output"] = [] + + yield StreamEvent( + event=f"on_{log_entry['type']}_{event_type}", + name=log_entry["name"], + run_id=log_entry["id"], + tags=log_entry["tags"], + metadata=log_entry["metadata"], + data=data, + ) - try: - # Yield each chunk from the output stream - if diff: - async for log in stream: - yield log - else: - state = RunLog(state=None) # type: ignore[arg-type] - async for log in stream: - state = state + log - yield state - finally: - # Wait for the runnable to finish, if not cancelled (eg. by break) - try: - await task - except asyncio.CancelledError: - pass + # Finally, we take care of the streaming output from the root chain + # if there is any. + state = run_log.state + if state["streamed_output"]: + num_chunks = len(state["streamed_output"]) + if num_chunks != 1: + raise AssertionError( + f"Expected exactly one chunk of streamed output, " + f"got {num_chunks} instead. This is impossible. " + f"Encountered in: {state['name']}" + ) + + data = {"chunk": state["streamed_output"][0]} + # Clean up the stream, we don't need it anymore. + state["streamed_output"] = [] + + event = StreamEvent( + event=f"on_{state['type']}_stream", + run_id=state["id"], + tags=root_tags, + metadata=root_metadata, + name=root_name, + data=data, + ) + if _root_event_filter.include_event(event, state["type"]): + yield event + + state = run_log.state + + # Finally yield the end event for the root runnable. + event = StreamEvent( + event=f"on_{state['type']}_end", + name=root_name, + run_id=state["id"], + tags=root_tags, + metadata=root_metadata, + data={ + "output": state["final_output"], + }, + ) + if _root_event_filter.include_event(event, state["type"]): + yield event def transform( self, @@ -3396,6 +3650,18 @@ async def ainvoke( ) -> List[Output]: return await self._acall_with_config(self._ainvoke, input, config, **kwargs) + async def astream_events( + self, + input: Input, + config: Optional[RunnableConfig] = None, + **kwargs: Optional[Any], + ) -> AsyncIterator[StreamEvent]: + for _ in range(1): + raise NotImplementedError( + "RunnableEach does not support astream_events yet." + ) + yield + class RunnableEach(RunnableEachBase[Input, Output]): """ @@ -3686,6 +3952,17 @@ async def astream( ): yield item + async def astream_events( + self, + input: Input, + config: Optional[RunnableConfig] = None, + **kwargs: Optional[Any], + ) -> AsyncIterator[StreamEvent]: + async for item in self.bound.astream_events( + input, self._merge_configs(config), **{**self.kwargs, **kwargs} + ): + yield item + def transform( self, input: Iterator[Input], diff --git a/libs/core/langchain_core/runnables/schema.py b/libs/core/langchain_core/runnables/schema.py new file mode 100644 index 0000000000000..b2891b10b11c6 --- /dev/null +++ b/libs/core/langchain_core/runnables/schema.py @@ -0,0 +1,133 @@ +"""Module contains typedefs that are used with runnables.""" +from __future__ import annotations + +from typing import Any, Dict, List + +from typing_extensions import NotRequired, TypedDict + + +class EventData(TypedDict, total=False): + """Data associated with a streaming event.""" + + input: Any + """The input passed to the runnable that generated the event. + + Inputs will sometimes be available at the *START* of the runnable, and + sometimes at the *END* of the runnable. + + If a runnable is able to stream its inputs, then its input by definition + won't be known until the *END* of the runnable when it has finished streaming + its inputs. + """ + output: Any + """The output of the runnable that generated the event. + + Outputs will only be available at the *END* of the runnable. + + For most runnables, this field can be inferred from the `chunk` field, + though there might be some exceptions for special cased runnables (e.g., like + chat models), which may return more information. + """ + chunk: Any + """A streaming chunk from the output that generated the event. + + chunks support addition in general, and adding them up should result + in the output of the runnable that generated the event. + """ + + +class StreamEvent(TypedDict): + """A streaming event. + + Schema of a streaming event which is produced from the astream_events method. + + Example: + + .. code-block:: python + + from langchain_core.runnables import RunnableLambda + + async def reverse(s: str) -> str: + return s[::-1] + + chain = RunnableLambda(func=reverse) + + events = [event async for event in chain.astream_events("hello")] + + # will produce the following events (run_id has been omitted for brevity): + [ + { + "data": {"input": "hello"}, + "event": "on_chain_start", + "metadata": {}, + "name": "reverse", + "tags": [], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "reverse", + "tags": [], + }, + { + "data": {"output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "reverse", + "tags": [], + }, + ] + """ + + event: str + """Event names are of the format: on_[runnable_type]_(start|stream|end). + + Runnable types are one of: + * llm - used by non chat models + * chat_model - used by chat models + * prompt -- e.g., ChatPromptTemplate + * tool -- from tools defined via @tool decorator or inheriting from Tool/BaseTool + * chain - most Runnables are of this type + + Further, the events are categorized as one of: + * start - when the runnable starts + * stream - when the runnable is streaming + * end - when the runnable ends + + start, stream and end are associated with slightly different `data` payload. + + Please see the documentation for `EventData` for more details. + """ + name: str + """The name of the runnable that generated the event.""" + run_id: str + """An randomly generated ID to keep track of the execution of the given runnable. + + Each child runnable that gets invoked as part of the execution of a parent runnable + is assigned its own unique ID. + """ + tags: NotRequired[List[str]] + """Tags associated with the runnable that generated this event. + + Tags are always inherited from parent runnables. + + Tags can either be bound to a runnable using `.with_config({"tags": ["hello"]})` + or passed at run time using `.astream_events(..., {"tags": ["hello"]})`. + """ + metadata: NotRequired[Dict[str, Any]] + """Metadata associated with the runnable that generated this event. + + Metadata can either be bound to a runnable using + + `.with_config({"metadata": { "foo": "bar" }})` + + or passed at run time using + + `.astream_events(..., {"metadata": {"foo": "bar"}})`. + """ + data: EventData + """Event data. + + The contents of the event data depend on the event type. + """ diff --git a/libs/core/langchain_core/runnables/utils.py b/libs/core/langchain_core/runnables/utils.py index bd629194b6c03..992ea077bf449 100644 --- a/libs/core/langchain_core/runnables/utils.py +++ b/libs/core/langchain_core/runnables/utils.py @@ -1,3 +1,4 @@ +"""Utility code for runnables.""" from __future__ import annotations import ast @@ -24,6 +25,8 @@ Union, ) +from langchain_core.runnables.schema import StreamEvent + Input = TypeVar("Input", contravariant=True) # Output type should implement __concat__, as eg str, list, dict do Output = TypeVar("Output", covariant=True) @@ -419,3 +422,58 @@ def get_unique_config_specs( f"for {id}: {[first] + others}" ) return unique + + +class _RootEventFilter: + def __init__( + self, + *, + include_names: Optional[Sequence[str]] = None, + include_types: Optional[Sequence[str]] = None, + include_tags: Optional[Sequence[str]] = None, + exclude_names: Optional[Sequence[str]] = None, + exclude_types: Optional[Sequence[str]] = None, + exclude_tags: Optional[Sequence[str]] = None, + ) -> None: + """Utility to filter the root event in the astream_events implementation. + + This is simply binding the arguments to the namespace to make save on + a bit of typing in the astream_events implementation. + """ + self.include_names = include_names + self.include_types = include_types + self.include_tags = include_tags + self.exclude_names = exclude_names + self.exclude_types = exclude_types + self.exclude_tags = exclude_tags + + def include_event(self, event: StreamEvent, root_type: str) -> bool: + """Determine whether to include an event.""" + if ( + self.include_names is None + and self.include_types is None + and self.include_tags is None + ): + include = True + else: + include = False + + event_tags = event.get("tags") or [] + + if self.include_names is not None: + include = include or event["name"] in self.include_names + if self.include_types is not None: + include = include or root_type in self.include_types + if self.include_tags is not None: + include = include or any(tag in self.include_tags for tag in event_tags) + + if self.exclude_names is not None: + include = include and event["name"] not in self.exclude_names + if self.exclude_types is not None: + include = include and root_type not in self.exclude_types + if self.exclude_tags is not None: + include = include and all( + tag not in self.exclude_tags for tag in event_tags + ) + + return include diff --git a/libs/core/langchain_core/tools.py b/libs/core/langchain_core/tools.py index 50cf500b93b1f..b83a5cc8a7b95 100644 --- a/libs/core/langchain_core/tools.py +++ b/libs/core/langchain_core/tools.py @@ -300,7 +300,7 @@ def _to_args_and_kwargs(self, tool_input: Union[str, Dict]) -> Tuple[Tuple, Dict def run( self, - tool_input: Union[str, Dict], + tool_input: Union[str, Dict[str, Any]], verbose: Optional[bool] = None, start_color: Optional[str] = "green", color: Optional[str] = "green", @@ -333,6 +333,11 @@ def run( tool_input if isinstance(tool_input, str) else str(tool_input), color=start_color, name=run_name, + # Inputs by definition should always be dicts. + # For now, it's unclear whether this assumption is ever violated, + # but if it is we will send a `None` value to the callback instead + # And will need to address issue via a patch. + inputs=None if isinstance(tool_input, str) else tool_input, **kwargs, ) try: @@ -407,6 +412,7 @@ async def arun( tool_input if isinstance(tool_input, str) else str(tool_input), color=start_color, name=run_name, + inputs=tool_input, **kwargs, ) try: diff --git a/libs/core/langchain_core/tracers/base.py b/libs/core/langchain_core/tracers/base.py index 8b93c42d7c2b1..0d4c4213135bc 100644 --- a/libs/core/langchain_core/tracers/base.py +++ b/libs/core/langchain_core/tracers/base.py @@ -11,8 +11,10 @@ Any, Dict, List, + Literal, Optional, Sequence, + Set, Union, cast, ) @@ -23,6 +25,7 @@ from langchain_core.callbacks.base import BaseCallbackHandler from langchain_core.exceptions import TracerException from langchain_core.load import dumpd +from langchain_core.messages import BaseMessage from langchain_core.outputs import ( ChatGeneration, ChatGenerationChunk, @@ -40,8 +43,29 @@ class BaseTracer(BaseCallbackHandler, ABC): """Base interface for tracers.""" - def __init__(self, **kwargs: Any) -> None: + def __init__( + self, + *, + _schema_format: Literal["original", "streaming_events"] = "original", + **kwargs: Any, + ) -> None: + """Initialize the tracer. + + Args: + _schema_format: Primarily changes how the inputs and outputs are + handled. For internal use only. This API will change. + - 'original' is the format used by all current tracers. + This format is slightly inconsistent with respect to inputs + and outputs. + - 'streaming_events' is used for supporting streaming events, + for internal usage. It will likely change in the future, or + be deprecated entirely in favor of a dedicated async tracer + for streaming events. + kwargs: Additional keyword arguments that will be passed to + the super class. + """ super().__init__(**kwargs) + self._schema_format = _schema_format # For internal use only API will change. self.run_map: Dict[str, Run] = {} @staticmethod @@ -134,17 +158,76 @@ def _get_execution_order(self, parent_run_id: Optional[str] = None) -> int: return parent_run.child_execution_order + 1 - def _get_run(self, run_id: UUID, run_type: Optional[str] = None) -> Run: + def _get_run( + self, run_id: UUID, run_type: Union[str, Set[str], None] = None + ) -> Run: try: run = self.run_map[str(run_id)] except KeyError as exc: raise TracerException(f"No indexed run ID {run_id}.") from exc - if run_type is not None and run.run_type != run_type: + + if isinstance(run_type, str): + run_types: Union[Set[str], None] = {run_type} + else: + run_types = run_type + if run_types is not None and run.run_type not in run_types: raise TracerException( - f"Found {run.run_type} run at ID {run_id}, but expected {run_type} run." + f"Found {run.run_type} run at ID {run_id}, " + f"but expected {run_types} run." ) return run + def on_chat_model_start( + self, + serialized: Dict[str, Any], + messages: List[List[BaseMessage]], + *, + run_id: UUID, + tags: Optional[List[str]] = None, + parent_run_id: Optional[UUID] = None, + metadata: Optional[Dict[str, Any]] = None, + name: Optional[str] = None, + **kwargs: Any, + ) -> Run: + """Start a trace for an LLM run.""" + if self._schema_format != "streaming_events": + # Please keep this un-implemented for backwards compatibility. + # When it's unimplemented old tracers that use the "original" format + # fallback on the on_llm_start method implementation if they + # find that the on_chat_model_start method is not implemented. + # This can eventually be cleaned up by writing a "modern" tracer + # that has all the updated schema changes corresponding to + # the "streaming_events" format. + raise NotImplementedError( + f"Chat model tracing is not supported in " + f"for {self._schema_format} format." + ) + parent_run_id_ = str(parent_run_id) if parent_run_id else None + execution_order = self._get_execution_order(parent_run_id_) + start_time = datetime.now(timezone.utc) + if metadata: + kwargs.update({"metadata": metadata}) + chat_model_run = Run( + id=run_id, + parent_run_id=parent_run_id, + serialized=serialized, + inputs={"messages": [[dumpd(msg) for msg in batch] for batch in messages]}, + extra=kwargs, + events=[{"name": "start", "time": start_time}], + start_time=start_time, + execution_order=execution_order, + child_execution_order=execution_order, + # WARNING: This is valid ONLY for streaming_events. + # run_type="llm" is what's used by virtually all tracers. + # Changing this to "chat_model" may break triggering on_llm_start + run_type="chat_model", + tags=tags, + name=name, + ) + self._start_trace(chat_model_run) + self._on_chat_model_start(chat_model_run) + return chat_model_run + def on_llm_start( self, serialized: Dict[str, Any], @@ -167,6 +250,7 @@ def on_llm_start( id=run_id, parent_run_id=parent_run_id, serialized=serialized, + # TODO: Figure out how to expose kwargs here inputs={"prompts": prompts}, extra=kwargs, events=[{"name": "start", "time": start_time}], @@ -191,7 +275,9 @@ def on_llm_new_token( **kwargs: Any, ) -> Run: """Run on new LLM token. Only available when streaming is enabled.""" - llm_run = self._get_run(run_id, run_type="llm") + # "chat_model" is only used for the experimental new streaming_events format. + # This change should not affect any existing tracers. + llm_run = self._get_run(run_id, run_type={"llm", "chat_model"}) event_kwargs: Dict[str, Any] = {"token": token} if chunk: event_kwargs["chunk"] = chunk @@ -238,7 +324,9 @@ def on_retry( def on_llm_end(self, response: LLMResult, *, run_id: UUID, **kwargs: Any) -> Run: """End a trace for an LLM run.""" - llm_run = self._get_run(run_id, run_type="llm") + # "chat_model" is only used for the experimental new streaming_events format. + # This change should not affect any existing tracers. + llm_run = self._get_run(run_id, run_type={"llm", "chat_model"}) llm_run.outputs = response.dict() for i, generations in enumerate(response.generations): for j, generation in enumerate(generations): @@ -261,7 +349,9 @@ def on_llm_error( **kwargs: Any, ) -> Run: """Handle an error for an LLM run.""" - llm_run = self._get_run(run_id, run_type="llm") + # "chat_model" is only used for the experimental new streaming_events format. + # This change should not affect any existing tracers. + llm_run = self._get_run(run_id, run_type={"llm", "chat_model"}) llm_run.error = self._get_stacktrace(error) llm_run.end_time = datetime.now(timezone.utc) llm_run.events.append({"name": "error", "time": llm_run.end_time}) @@ -292,7 +382,7 @@ def on_chain_start( id=run_id, parent_run_id=parent_run_id, serialized=serialized, - inputs=inputs if isinstance(inputs, dict) else {"input": inputs}, + inputs=self._get_chain_inputs(inputs), extra=kwargs, events=[{"name": "start", "time": start_time}], start_time=start_time, @@ -307,6 +397,28 @@ def on_chain_start( self._on_chain_start(chain_run) return chain_run + def _get_chain_inputs(self, inputs: Any) -> Any: + """Get the inputs for a chain run.""" + if self._schema_format == "original": + return inputs if isinstance(inputs, dict) else {"input": inputs} + elif self._schema_format == "streaming_events": + return { + "input": inputs, + } + else: + raise ValueError(f"Invalid format: {self._schema_format}") + + def _get_chain_outputs(self, outputs: Any) -> Any: + """Get the outputs for a chain run.""" + if self._schema_format == "original": + return outputs if isinstance(outputs, dict) else {"output": outputs} + elif self._schema_format == "streaming_events": + return { + "output": outputs, + } + else: + raise ValueError(f"Invalid format: {self._schema_format}") + def on_chain_end( self, outputs: Dict[str, Any], @@ -317,13 +429,11 @@ def on_chain_end( ) -> Run: """End a trace for a chain run.""" chain_run = self._get_run(run_id) - chain_run.outputs = ( - outputs if isinstance(outputs, dict) else {"output": outputs} - ) + chain_run.outputs = self._get_chain_outputs(outputs) chain_run.end_time = datetime.now(timezone.utc) chain_run.events.append({"name": "end", "time": chain_run.end_time}) if inputs is not None: - chain_run.inputs = inputs if isinstance(inputs, dict) else {"input": inputs} + chain_run.inputs = self._get_chain_inputs(inputs) self._end_trace(chain_run) self._on_chain_end(chain_run) return chain_run @@ -342,7 +452,7 @@ def on_chain_error( chain_run.end_time = datetime.now(timezone.utc) chain_run.events.append({"name": "error", "time": chain_run.end_time}) if inputs is not None: - chain_run.inputs = inputs if isinstance(inputs, dict) else {"input": inputs} + chain_run.inputs = self._get_chain_inputs(inputs) self._end_trace(chain_run) self._on_chain_error(chain_run) return chain_run @@ -357,6 +467,7 @@ def on_tool_start( parent_run_id: Optional[UUID] = None, metadata: Optional[Dict[str, Any]] = None, name: Optional[str] = None, + inputs: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Run: """Start a trace for a tool run.""" @@ -365,11 +476,20 @@ def on_tool_start( start_time = datetime.now(timezone.utc) if metadata: kwargs.update({"metadata": metadata}) + + if self._schema_format == "original": + inputs = {"input": input_str} + elif self._schema_format == "streaming_events": + inputs = {"input": inputs} + else: + raise AssertionError(f"Invalid format: {self._schema_format}") + tool_run = Run( id=run_id, parent_run_id=parent_run_id, serialized=serialized, - inputs={"input": input_str}, + # Wrapping in dict since Run requires a dict object. + inputs=inputs, extra=kwargs, events=[{"name": "start", "time": start_time}], start_time=start_time, diff --git a/libs/core/langchain_core/tracers/langchain.py b/libs/core/langchain_core/tracers/langchain.py index d5daf108085f5..947e10f7e6be1 100644 --- a/libs/core/langchain_core/tracers/langchain.py +++ b/libs/core/langchain_core/tracers/langchain.py @@ -112,7 +112,7 @@ def on_chat_model_start( metadata: Optional[Dict[str, Any]] = None, name: Optional[str] = None, **kwargs: Any, - ) -> None: + ) -> Run: """Start a trace for an LLM run.""" parent_run_id_ = str(parent_run_id) if parent_run_id else None execution_order = self._get_execution_order(parent_run_id_) @@ -135,6 +135,7 @@ def on_chat_model_start( ) self._start_trace(chat_model_run) self._on_chat_model_start(chat_model_run) + return chat_model_run def _persist_run(self, run: Run) -> None: run_ = run.copy() diff --git a/libs/core/langchain_core/tracers/log_stream.py b/libs/core/langchain_core/tracers/log_stream.py index 9287ce8875ef2..72d7f590a2fde 100644 --- a/libs/core/langchain_core/tracers/log_stream.py +++ b/libs/core/langchain_core/tracers/log_stream.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import copy import math import threading @@ -9,19 +10,24 @@ AsyncIterator, Dict, List, + Literal, Optional, Sequence, - TypedDict, TypeVar, Union, + overload, ) from uuid import UUID import jsonpatch # type: ignore[import] from anyio import create_memory_object_stream +from typing_extensions import NotRequired, TypedDict +from langchain_core.load import dumps from langchain_core.load.load import load from langchain_core.outputs import ChatGenerationChunk, GenerationChunk +from langchain_core.runnables import Runnable, RunnableConfig, ensure_config +from langchain_core.runnables.utils import Input, Output from langchain_core.tracers.base import BaseTracer from langchain_core.tracers.schemas import Run @@ -46,8 +52,11 @@ class LogEntry(TypedDict): """List of LLM tokens streamed by this run, if applicable.""" streamed_output: List[Any] """List of output chunks streamed by this run, if available.""" + inputs: NotRequired[Optional[Any]] + """Inputs to this run. Not available currently via astream_log.""" final_output: Optional[Any] - """Final output of this run. + """Final output of this run. + Only available after the run has finished successfully.""" end_time: Optional[str] """ISO-8601 timestamp of when the run ended. @@ -65,6 +74,14 @@ class RunState(TypedDict): """Final output of the run, usually the result of aggregating (`+`) streamed_output. Updated throughout the run when supported by the Runnable.""" + name: str + """Name of the object being run.""" + type: str + """Type of the object being run, eg. prompt, chain, llm, etc.""" + + # Do we want tags/metadata on the root run? Client kinda knows it in most situations + # tags: List[str] + logs: Dict[str, LogEntry] """Map of run names to sub-runs. If filters were supplied, this list will contain only the runs that matched the filters.""" @@ -128,6 +145,15 @@ def __repr__(self) -> str: return f"RunLog({pformat(self.state)})" + def __eq__(self, other: object) -> bool: + # First compare that the state is the same + if not isinstance(other, RunLog): + return False + if self.state != other.state: + return False + # Then compare that the ops are the same + return super().__eq__(other) + T = TypeVar("T") @@ -145,8 +171,36 @@ def __init__( exclude_names: Optional[Sequence[str]] = None, exclude_types: Optional[Sequence[str]] = None, exclude_tags: Optional[Sequence[str]] = None, + # Schema format is for internal use only. + _schema_format: Literal["original", "streaming_events"] = "streaming_events", ) -> None: - super().__init__() + """A tracer that streams run logs to a stream. + + Args: + auto_close: Whether to close the stream when the root run finishes. + include_names: Only include runs from Runnables with matching names. + include_types: Only include runs from Runnables with matching types. + include_tags: Only include runs from Runnables with matching tags. + exclude_names: Exclude runs from Runnables with matching names. + exclude_types: Exclude runs from Runnables with matching types. + exclude_tags: Exclude runs from Runnables with matching tags. + _schema_format: Primarily changes how the inputs and outputs are + handled. + **For internal use only. This API will change.** + - 'original' is the format used by all current tracers. + This format is slightly inconsistent with respect to inputs + and outputs. + - 'streaming_events' is used for supporting streaming events, + for internal usage. It will likely change in the future, or + be deprecated entirely in favor of a dedicated async tracer + for streaming events. + """ + if _schema_format not in {"original", "streaming_events"}: + raise ValueError( + f"Invalid schema format: {_schema_format}. " + f"Expected one of 'original', 'streaming_events'." + ) + super().__init__(_schema_format=_schema_format) self.auto_close = auto_close self.include_names = include_names @@ -241,6 +295,8 @@ def _on_run_create(self, run: Run) -> None: streamed_output=[], final_output=None, logs={}, + name=run.name, + type=run.run_type, ), } ) @@ -257,24 +313,30 @@ def _on_run_create(self, run: Run) -> None: run.name if count == 1 else f"{run.name}:{count}" ) + entry = LogEntry( + id=str(run.id), + name=run.name, + type=run.run_type, + tags=run.tags or [], + metadata=(run.extra or {}).get("metadata", {}), + start_time=run.start_time.isoformat(timespec="milliseconds"), + streamed_output=[], + streamed_output_str=[], + final_output=None, + end_time=None, + ) + + if self._schema_format == "streaming_events": + # If using streaming events let's add inputs as well + entry["inputs"] = _get_standardized_inputs(run, self._schema_format) + # Add the run to the stream self.send_stream.send_nowait( RunLogPatch( { "op": "add", "path": f"/logs/{self._key_map_by_run_id[run.id]}", - "value": LogEntry( - id=str(run.id), - name=run.name, - type=run.run_type, - tags=run.tags or [], - metadata=(run.extra or {}).get("metadata", {}), - start_time=run.start_time.isoformat(timespec="milliseconds"), - streamed_output=[], - streamed_output_str=[], - final_output=None, - end_time=None, - ), + "value": entry, } ) ) @@ -287,13 +349,28 @@ def _on_run_update(self, run: Run) -> None: if index is None: return - self.send_stream.send_nowait( - RunLogPatch( + ops = [] + + if self._schema_format == "streaming_events": + ops.append( + { + "op": "replace", + "path": f"/logs/{index}/inputs", + "value": _get_standardized_inputs(run, self._schema_format), + } + ) + + ops.extend( + [ + # Replace 'inputs' with final inputs + # This is needed because in many cases the inputs are not + # known until after the run is finished and the entire + # input stream has been processed by the runnable. { "op": "add", "path": f"/logs/{index}/final_output", # to undo the dumpd done by some runnables / tracer / etc - "value": load(run.outputs), + "value": _get_standardized_outputs(run, self._schema_format), }, { "op": "add", @@ -302,8 +379,10 @@ def _on_run_update(self, run: Run) -> None: if run.end_time is not None else None, }, - ) + ] ) + + self.send_stream.send_nowait(RunLogPatch(*ops)) finally: if run.id == self.root_id: if self.auto_close: @@ -337,3 +416,197 @@ def _on_llm_new_token( }, ) ) + + +def _get_standardized_inputs( + run: Run, schema_format: Literal["original", "streaming_events"] +) -> Optional[Dict[str, Any]]: + """Extract standardized inputs from a run. + + Standardizes the inputs based on the type of the runnable used. + + Args: + run: Run object + schema_format: The schema format to use. + + Returns: + Valid inputs are only dict. By conventions, inputs always represented + invocation using named arguments. + A None means that the input is not yet known! + """ + if schema_format == "original": + raise NotImplementedError( + "Do not assign inputs with original schema drop the key for now." + "When inputs are added to astream_log they should be added with " + "standardized schema for streaming events." + ) + + inputs = load(run.inputs) + + if run.run_type in {"retriever", "llm", "chat_model"}: + return inputs + + # new style chains + # These nest an additional 'input' key inside the 'inputs' to make sure + # the input is always a dict. We need to unpack and user the inner value. + inputs = inputs["input"] + # We should try to fix this in Runnables and callbacks/tracers + # Runnables should be using a None type here not a placeholder + # dict. + if inputs == {"input": ""}: # Workaround for Runnables not using None + # The input is not known, so we don't assign data['input'] + return None + return inputs + + +def _get_standardized_outputs( + run: Run, schema_format: Literal["original", "streaming_events"] +) -> Optional[Any]: + """Extract standardized output from a run. + + Standardizes the outputs based on the type of the runnable used. + + Args: + log: The log entry. + schema_format: The schema format to use. + + Returns: + An output if returned, otherwise a None + """ + outputs = load(run.outputs) + if schema_format == "original": + # Return the old schema, without standardizing anything + return outputs + + if run.run_type in {"retriever", "llm", "chat_model"}: + return outputs + + if isinstance(outputs, dict): + return outputs.get("output", None) + + return None + + +@overload +def _astream_log_implementation( + runnable: Runnable[Input, Output], + input: Any, + config: Optional[RunnableConfig] = None, + *, + stream: LogStreamCallbackHandler, + diff: Literal[True] = True, + with_streamed_output_list: bool = True, + **kwargs: Any, +) -> AsyncIterator[RunLogPatch]: + ... + + +@overload +def _astream_log_implementation( + runnable: Runnable[Input, Output], + input: Any, + config: Optional[RunnableConfig] = None, + *, + stream: LogStreamCallbackHandler, + diff: Literal[False], + with_streamed_output_list: bool = True, + **kwargs: Any, +) -> AsyncIterator[RunLog]: + ... + + +async def _astream_log_implementation( + runnable: Runnable[Input, Output], + input: Any, + config: Optional[RunnableConfig] = None, + *, + stream: LogStreamCallbackHandler, + diff: bool = True, + with_streamed_output_list: bool = True, + **kwargs: Any, +) -> Union[AsyncIterator[RunLogPatch], AsyncIterator[RunLog]]: + """Implementation of astream_log for a given runnable. + + The implementation has been factored out (at least temporarily) as both + astream_log and astream_events relies on it. + """ + import jsonpatch # type: ignore[import] + + from langchain_core.callbacks.base import BaseCallbackManager + from langchain_core.tracers.log_stream import ( + RunLog, + RunLogPatch, + ) + + # Assign the stream handler to the config + config = ensure_config(config) + callbacks = config.get("callbacks") + if callbacks is None: + config["callbacks"] = [stream] + elif isinstance(callbacks, list): + config["callbacks"] = callbacks + [stream] + elif isinstance(callbacks, BaseCallbackManager): + callbacks = callbacks.copy() + callbacks.add_handler(stream, inherit=True) + config["callbacks"] = callbacks + else: + raise ValueError( + f"Unexpected type for callbacks: {callbacks}." + "Expected None, list or AsyncCallbackManager." + ) + + # Call the runnable in streaming mode, + # add each chunk to the output stream + async def consume_astream() -> None: + try: + prev_final_output: Optional[Output] = None + final_output: Optional[Output] = None + + async for chunk in runnable.astream(input, config, **kwargs): + prev_final_output = final_output + if final_output is None: + final_output = chunk + else: + try: + final_output = final_output + chunk # type: ignore + except TypeError: + final_output = chunk + patches: List[Dict[str, Any]] = [] + if with_streamed_output_list: + patches.append( + { + "op": "add", + "path": "/streamed_output/-", + # chunk cannot be shared between + # streamed_output and final_output + # otherwise jsonpatch.apply will + # modify both + "value": copy.deepcopy(chunk), + } + ) + for op in jsonpatch.JsonPatch.from_diff( + prev_final_output, final_output, dumps=dumps + ): + patches.append({**op, "path": f"/final_output{op['path']}"}) + await stream.send_stream.send(RunLogPatch(*patches)) + finally: + await stream.send_stream.aclose() + + # Start the runnable in a task, so we can start consuming output + task = asyncio.create_task(consume_astream()) + try: + # Yield each chunk from the output stream + if diff: + async for log in stream: + yield log + else: + state = RunLog(state=None) # type: ignore[arg-type] + async for log in stream: + state = state + log + yield state + finally: + # Wait for the runnable to finish, if not cancelled (eg. by break) + try: + await task + except asyncio.CancelledError: + pass diff --git a/libs/core/tests/unit_tests/runnables/test_runnable.py b/libs/core/tests/unit_tests/runnables/test_runnable.py index 49e729b595f0f..a6af8ada3a22c 100644 --- a/libs/core/tests/unit_tests/runnables/test_runnable.py +++ b/libs/core/tests/unit_tests/runnables/test_runnable.py @@ -1647,6 +1647,8 @@ async def test_prompt() -> None: ] ) ], + "type": "prompt", + "name": "ChatPromptTemplate", }, ) @@ -2095,6 +2097,8 @@ async def test_prompt_with_llm( "logs": {}, "final_output": None, "streamed_output": [], + "name": "RunnableSequence", + "type": "chain", }, } ), @@ -2297,6 +2301,8 @@ async def test_prompt_with_llm_parser( "logs": {}, "final_output": None, "streamed_output": [], + "name": "RunnableSequence", + "type": "chain", }, } ), @@ -2508,7 +2514,13 @@ async def list_producer(input: AsyncIterator[Any]) -> AsyncIterator[AddableDict] { "op": "replace", "path": "", - "value": {"final_output": None, "logs": {}, "streamed_output": []}, + "value": { + "final_output": None, + "logs": {}, + "streamed_output": [], + "name": "list_producer", + "type": "chain", + }, } ), RunLogPatch( @@ -2536,12 +2548,14 @@ async def list_producer(input: AsyncIterator[Any]) -> AsyncIterator[AddableDict] assert state.state == { "final_output": {"alist": ["0", "1", "2", "3"]}, "logs": {}, + "name": "list_producer", "streamed_output": [ {"alist": ["0"]}, {"alist": ["1"]}, {"alist": ["2"]}, {"alist": ["3"]}, ], + "type": "chain", } @@ -5139,4 +5153,6 @@ def add_one(x: int) -> int: "final_output": 2, "logs": {}, "streamed_output": [2], + "name": "add_one", + "type": "chain", } diff --git a/libs/core/tests/unit_tests/runnables/test_runnable_events.py b/libs/core/tests/unit_tests/runnables/test_runnable_events.py new file mode 100644 index 0000000000000..2570363b34d72 --- /dev/null +++ b/libs/core/tests/unit_tests/runnables/test_runnable_events.py @@ -0,0 +1,1065 @@ +"""Module that contains tests for runnable.astream_events API.""" +from itertools import cycle +from typing import AsyncIterator, List, Sequence, cast + +import pytest + +from langchain_core.callbacks import CallbackManagerForRetrieverRun, Callbacks +from langchain_core.documents import Document +from langchain_core.messages import ( + AIMessage, + AIMessageChunk, + HumanMessage, + SystemMessage, +) +from langchain_core.prompt_values import ChatPromptValue +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.retrievers import BaseRetriever +from langchain_core.runnables import ( + RunnableLambda, +) +from langchain_core.runnables.schema import StreamEvent +from langchain_core.tools import tool +from tests.unit_tests.fake.chat_model import GenericFakeChatModel +from tests.unit_tests.fake.llm import FakeStreamingListLLM + + +def _with_nulled_run_id(events: Sequence[StreamEvent]) -> List[StreamEvent]: + """Removes the run ids from events.""" + return cast(List[StreamEvent], [{**event, "run_id": ""} for event in events]) + + +async def _as_async_iterator(iterable: List) -> AsyncIterator: + """Converts an iterable into an async iterator.""" + for item in iterable: + yield item + + +async def _collect_events(events: AsyncIterator[StreamEvent]) -> List[StreamEvent]: + """Collect the events and remove the run ids.""" + materialized_events = [event async for event in events] + events_ = _with_nulled_run_id(materialized_events) + for event in events_: + event["tags"] = sorted(event["tags"]) + return events_ + + +async def test_event_stream_with_single_lambda() -> None: + """Test the event stream with a tool.""" + + def reverse(s: str) -> str: + """Reverse a string.""" + return s[::-1] + + chain = RunnableLambda(func=reverse) + + events = await _collect_events(chain.astream_events("hello")) + assert events == [ + { + "data": {"input": "hello"}, + "event": "on_chain_start", + "metadata": {}, + "name": "reverse", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "reverse", + "run_id": "", + "tags": [], + }, + { + "data": {"output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "reverse", + "run_id": "", + "tags": [], + }, + ] + + +async def test_event_stream_with_triple_lambda() -> None: + def reverse(s: str) -> str: + """Reverse a string.""" + return s[::-1] + + r = RunnableLambda(func=reverse) + + chain = ( + r.with_config({"run_name": "1"}) + | r.with_config({"run_name": "2"}) + | r.with_config({"run_name": "3"}) + ) + events = await _collect_events(chain.astream_events("hello")) + assert events == [ + { + "data": {"input": "hello"}, + "event": "on_chain_start", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "1", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "1", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "2", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"input": "hello", "output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "1", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"chunk": "hello"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "2", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "3", + "run_id": "", + "tags": ["seq:step:3"], + }, + { + "data": {"input": "olleh", "output": "hello"}, + "event": "on_chain_end", + "metadata": {}, + "name": "2", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "3", + "run_id": "", + "tags": ["seq:step:3"], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"input": "hello", "output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "3", + "run_id": "", + "tags": ["seq:step:3"], + }, + { + "data": {"output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + ] + + +async def test_event_stream_with_triple_lambda_test_filtering() -> None: + """Test filtering based on tags / names""" + + def reverse(s: str) -> str: + """Reverse a string.""" + return s[::-1] + + r = RunnableLambda(func=reverse) + + chain = ( + r.with_config({"run_name": "1"}) + | r.with_config({"run_name": "2", "tags": ["my_tag"]}) + | r.with_config({"run_name": "3", "tags": ["my_tag"]}) + ) + events = await _collect_events(chain.astream_events("hello", include_names=["1"])) + assert events == [ + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "1", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "1", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"input": "hello", "output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "1", + "run_id": "", + "tags": ["seq:step:1"], + }, + ] + + events = await _collect_events( + chain.astream_events("hello", include_tags=["my_tag"], exclude_names=["2"]) + ) + assert events == [ + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "3", + "run_id": "", + "tags": ["my_tag", "seq:step:3"], + }, + { + "data": {"chunk": "olleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "3", + "run_id": "", + "tags": ["my_tag", "seq:step:3"], + }, + { + "data": {"input": "hello", "output": "olleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "3", + "run_id": "", + "tags": ["my_tag", "seq:step:3"], + }, + ] + + +async def test_event_stream_with_lambdas_from_lambda() -> None: + as_lambdas = RunnableLambda(lambda x: {"answer": "goodbye"}).with_config( + {"run_name": "my_lambda"} + ) + events = await _collect_events(as_lambdas.astream_events({"question": "hello"})) + assert events == [ + { + "data": {"input": {"question": "hello"}}, + "event": "on_chain_start", + "metadata": {}, + "name": "my_lambda", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": {"answer": "goodbye"}}, + "event": "on_chain_stream", + "metadata": {}, + "name": "my_lambda", + "run_id": "", + "tags": [], + }, + { + "data": {"output": {"answer": "goodbye"}}, + "event": "on_chain_end", + "metadata": {}, + "name": "my_lambda", + "run_id": "", + "tags": [], + }, + ] + + +async def test_event_stream_with_simple_chain() -> None: + """Test as event stream.""" + template = ChatPromptTemplate.from_messages( + [("system", "You are Cat Agent 007"), ("human", "{question}")] + ).with_config({"run_name": "my_template", "tags": ["my_template"]}) + + infinite_cycle = cycle( + [AIMessage(content="hello world!"), AIMessage(content="goodbye world!")] + ) + # When streaming GenericFakeChatModel breaks AIMessage into chunks based on spaces + model = ( + GenericFakeChatModel(messages=infinite_cycle) + .with_config( + { + "metadata": {"a": "b"}, + "tags": ["my_model"], + "run_name": "my_model", + } + ) + .bind(stop="<stop_token>") + ) + + chain = (template | model).with_config( + { + "metadata": {"foo": "bar"}, + "tags": ["my_chain"], + "run_name": "my_chain", + } + ) + + events = await _collect_events(chain.astream_events({"question": "hello"})) + assert events == [ + { + "data": {"input": {"question": "hello"}}, + "event": "on_chain_start", + "metadata": {"foo": "bar"}, + "name": "my_chain", + "run_id": "", + "tags": ["my_chain"], + }, + { + "data": {"input": {"question": "hello"}}, + "event": "on_prompt_start", + "metadata": {"foo": "bar"}, + "name": "my_template", + "run_id": "", + "tags": ["my_chain", "my_template", "seq:step:1"], + }, + { + "data": { + "input": {"question": "hello"}, + "output": ChatPromptValue( + messages=[ + SystemMessage(content="You are Cat Agent 007"), + HumanMessage(content="hello"), + ] + ), + }, + "event": "on_prompt_end", + "metadata": {"foo": "bar"}, + "name": "my_template", + "run_id": "", + "tags": ["my_chain", "my_template", "seq:step:1"], + }, + { + "data": { + "input": { + "messages": [ + [ + SystemMessage(content="You are Cat Agent 007"), + HumanMessage(content="hello"), + ] + ] + } + }, + "event": "on_chat_model_start", + "metadata": {"a": "b", "foo": "bar"}, + "name": "my_model", + "run_id": "", + "tags": ["my_chain", "my_model", "seq:step:2"], + }, + { + "data": {"chunk": AIMessageChunk(content="hello")}, + "event": "on_chain_stream", + "metadata": {"foo": "bar"}, + "name": "my_chain", + "run_id": "", + "tags": ["my_chain"], + }, + { + "data": {"chunk": AIMessageChunk(content="hello")}, + "event": "on_chat_model_stream", + "metadata": {"a": "b", "foo": "bar"}, + "name": "my_model", + "run_id": "", + "tags": ["my_chain", "my_model", "seq:step:2"], + }, + { + "data": {"chunk": AIMessageChunk(content=" ")}, + "event": "on_chain_stream", + "metadata": {"foo": "bar"}, + "name": "my_chain", + "run_id": "", + "tags": ["my_chain"], + }, + { + "data": {"chunk": AIMessageChunk(content=" ")}, + "event": "on_chat_model_stream", + "metadata": {"a": "b", "foo": "bar"}, + "name": "my_model", + "run_id": "", + "tags": ["my_chain", "my_model", "seq:step:2"], + }, + { + "data": {"chunk": AIMessageChunk(content="world!")}, + "event": "on_chain_stream", + "metadata": {"foo": "bar"}, + "name": "my_chain", + "run_id": "", + "tags": ["my_chain"], + }, + { + "data": {"chunk": AIMessageChunk(content="world!")}, + "event": "on_chat_model_stream", + "metadata": {"a": "b", "foo": "bar"}, + "name": "my_model", + "run_id": "", + "tags": ["my_chain", "my_model", "seq:step:2"], + }, + { + "data": { + "input": { + "messages": [ + [ + SystemMessage(content="You are Cat Agent 007"), + HumanMessage(content="hello"), + ] + ] + }, + "output": { + "generations": [ + [ + { + "generation_info": None, + "message": AIMessageChunk(content="hello world!"), + "text": "hello world!", + "type": "ChatGenerationChunk", + } + ] + ], + "llm_output": None, + "run": None, + }, + }, + "event": "on_chat_model_end", + "metadata": {"a": "b", "foo": "bar"}, + "name": "my_model", + "run_id": "", + "tags": ["my_chain", "my_model", "seq:step:2"], + }, + { + "data": {"output": AIMessageChunk(content="hello world!")}, + "event": "on_chain_end", + "metadata": {"foo": "bar"}, + "name": "my_chain", + "run_id": "", + "tags": ["my_chain"], + }, + ] + + +async def test_event_streaming_with_tools() -> None: + """Test streaming events with different tool definitions.""" + + @tool + def parameterless() -> str: + """A tool that does nothing.""" + return "hello" + + @tool + def with_callbacks(callbacks: Callbacks) -> str: + """A tool that does nothing.""" + return "world" + + @tool + def with_parameters(x: int, y: str) -> dict: + """A tool that does nothing.""" + return {"x": x, "y": y} + + @tool + def with_parameters_and_callbacks(x: int, y: str, callbacks: Callbacks) -> dict: + """A tool that does nothing.""" + return {"x": x, "y": y} + + # type ignores below because the tools don't appear to be runnables to type checkers + # we can remove as soon as that's fixed + events = await _collect_events(parameterless.astream_events({})) # type: ignore + assert events == [ + { + "data": {"input": {}}, + "event": "on_tool_start", + "metadata": {}, + "name": "parameterless", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": "hello"}, + "event": "on_tool_stream", + "metadata": {}, + "name": "parameterless", + "run_id": "", + "tags": [], + }, + { + "data": {"output": "hello"}, + "event": "on_tool_end", + "metadata": {}, + "name": "parameterless", + "run_id": "", + "tags": [], + }, + ] + + events = await _collect_events(with_callbacks.astream_events({})) # type: ignore + assert events == [ + { + "data": {"input": {}}, + "event": "on_tool_start", + "metadata": {}, + "name": "with_callbacks", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": "world"}, + "event": "on_tool_stream", + "metadata": {}, + "name": "with_callbacks", + "run_id": "", + "tags": [], + }, + { + "data": {"output": "world"}, + "event": "on_tool_end", + "metadata": {}, + "name": "with_callbacks", + "run_id": "", + "tags": [], + }, + ] + events = await _collect_events(with_parameters.astream_events({"x": 1, "y": "2"})) # type: ignore + assert events == [ + { + "data": {"input": {"x": 1, "y": "2"}}, + "event": "on_tool_start", + "metadata": {}, + "name": "with_parameters", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": {"x": 1, "y": "2"}}, + "event": "on_tool_stream", + "metadata": {}, + "name": "with_parameters", + "run_id": "", + "tags": [], + }, + { + "data": {"output": {"x": 1, "y": "2"}}, + "event": "on_tool_end", + "metadata": {}, + "name": "with_parameters", + "run_id": "", + "tags": [], + }, + ] + + events = await _collect_events( + with_parameters_and_callbacks.astream_events({"x": 1, "y": "2"}) # type: ignore + ) + assert events == [ + { + "data": {"input": {"x": 1, "y": "2"}}, + "event": "on_tool_start", + "metadata": {}, + "name": "with_parameters_and_callbacks", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": {"x": 1, "y": "2"}}, + "event": "on_tool_stream", + "metadata": {}, + "name": "with_parameters_and_callbacks", + "run_id": "", + "tags": [], + }, + { + "data": {"output": {"x": 1, "y": "2"}}, + "event": "on_tool_end", + "metadata": {}, + "name": "with_parameters_and_callbacks", + "run_id": "", + "tags": [], + }, + ] + + +class HardCodedRetriever(BaseRetriever): + documents: List[Document] + + def _get_relevant_documents( + self, query: str, *, run_manager: CallbackManagerForRetrieverRun + ) -> List[Document]: + return self.documents + + +async def test_event_stream_with_retriever() -> None: + """Test the event stream with a retriever.""" + retriever = HardCodedRetriever( + documents=[ + Document( + page_content="hello world!", + metadata={"foo": "bar"}, + ), + Document( + page_content="goodbye world!", + metadata={"food": "spare"}, + ), + ] + ) + events = await _collect_events(retriever.astream_events({"query": "hello"})) + assert events == [ + { + "data": { + "input": {"query": "hello"}, + }, + "event": "on_retriever_start", + "metadata": {}, + "name": "HardCodedRetriever", + "run_id": "", + "tags": [], + }, + { + "data": { + "chunk": [ + Document(page_content="hello world!", metadata={"foo": "bar"}), + Document(page_content="goodbye world!", metadata={"food": "spare"}), + ] + }, + "event": "on_retriever_stream", + "metadata": {}, + "name": "HardCodedRetriever", + "run_id": "", + "tags": [], + }, + { + "data": { + "output": [ + Document(page_content="hello world!", metadata={"foo": "bar"}), + Document(page_content="goodbye world!", metadata={"food": "spare"}), + ], + }, + "event": "on_retriever_end", + "metadata": {}, + "name": "HardCodedRetriever", + "run_id": "", + "tags": [], + }, + ] + + +async def test_event_stream_with_retriever_and_formatter() -> None: + """Test the event stream with a retriever.""" + retriever = HardCodedRetriever( + documents=[ + Document( + page_content="hello world!", + metadata={"foo": "bar"}, + ), + Document( + page_content="goodbye world!", + metadata={"food": "spare"}, + ), + ] + ) + + def format_docs(docs: List[Document]) -> str: + """Format the docs.""" + return ", ".join([doc.page_content for doc in docs]) + + chain = retriever | format_docs + events = await _collect_events(chain.astream_events("hello")) + assert events == [ + { + "data": {"input": "hello"}, + "event": "on_chain_start", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"input": {"query": "hello"}}, + "event": "on_retriever_start", + "metadata": {}, + "name": "Retriever", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": { + "input": {"query": "hello"}, + "output": { + "documents": [ + Document(page_content="hello world!", metadata={"foo": "bar"}), + Document( + page_content="goodbye world!", metadata={"food": "spare"} + ), + ] + }, + }, + "event": "on_retriever_end", + "metadata": {}, + "name": "Retriever", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "format_docs", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"chunk": "hello world!, goodbye world!"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "format_docs", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"chunk": "hello world!, goodbye world!"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": { + "input": [ + Document(page_content="hello world!", metadata={"foo": "bar"}), + Document(page_content="goodbye world!", metadata={"food": "spare"}), + ], + "output": "hello world!, goodbye world!", + }, + "event": "on_chain_end", + "metadata": {}, + "name": "format_docs", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"output": "hello world!, goodbye world!"}, + "event": "on_chain_end", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + ] + + +async def test_event_stream_on_chain_with_tool() -> None: + """Test the event stream with a tool.""" + + @tool + def concat(a: str, b: str) -> str: + """A tool that does nothing.""" + return a + b + + def reverse(s: str) -> str: + """Reverse a string.""" + return s[::-1] + + # For whatever reason type annotations fail here because reverse + # does not appear to be a runnable + chain = concat | reverse # type: ignore + + events = await _collect_events(chain.astream_events({"a": "hello", "b": "world"})) + assert events == [ + { + "data": {"input": {"a": "hello", "b": "world"}}, + "event": "on_chain_start", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"input": {"a": "hello", "b": "world"}}, + "event": "on_tool_start", + "metadata": {}, + "name": "concat", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"input": {"a": "hello", "b": "world"}, "output": "helloworld"}, + "event": "on_tool_end", + "metadata": {}, + "name": "concat", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "reverse", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"chunk": "dlrowolleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "reverse", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"chunk": "dlrowolleh"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"input": "helloworld", "output": "dlrowolleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "reverse", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"output": "dlrowolleh"}, + "event": "on_chain_end", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + ] + + +async def test_event_stream_with_retry() -> None: + """Test the event stream with a tool.""" + + def success(inputs: str) -> str: + return "success" + + def fail(inputs: str) -> None: + """Simple func.""" + raise Exception("fail") + + chain = RunnableLambda(success) | RunnableLambda(fail).with_retry( + stop_after_attempt=1, + ) + iterable = chain.astream_events("q") + + events = [] + + for _ in range(10): + try: + next_chunk = await iterable.__anext__() + events.append(next_chunk) + except Exception: + break + + events = _with_nulled_run_id(events) + for event in events: + event["tags"] = sorted(event["tags"]) + + assert events == [ + { + "data": {"input": "q"}, + "event": "on_chain_start", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "success", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"chunk": "success"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "success", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {}, + "event": "on_chain_start", + "metadata": {}, + "name": "fail", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"input": "q", "output": "success"}, + "event": "on_chain_end", + "metadata": {}, + "name": "success", + "run_id": "", + "tags": ["seq:step:1"], + }, + { + "data": {"input": "success", "output": None}, + "event": "on_chain_end", + "metadata": {}, + "name": "fail", + "run_id": "", + "tags": ["seq:step:2"], + }, + ] + + +async def test_with_llm() -> None: + """Test with regular llm.""" + prompt = ChatPromptTemplate.from_messages( + [("system", "You are Cat Agent 007"), ("human", "{question}")] + ).with_config({"run_name": "my_template", "tags": ["my_template"]}) + llm = FakeStreamingListLLM(responses=["abc"]) + + chain = prompt | llm + events = await _collect_events(chain.astream_events({"question": "hello"})) + assert events == [ + { + "data": {"input": {"question": "hello"}}, + "event": "on_chain_start", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"input": {"question": "hello"}}, + "event": "on_prompt_start", + "metadata": {}, + "name": "my_template", + "run_id": "", + "tags": ["my_template", "seq:step:1"], + }, + { + "data": { + "input": {"question": "hello"}, + "output": ChatPromptValue( + messages=[ + SystemMessage(content="You are Cat Agent 007"), + HumanMessage(content="hello"), + ] + ), + }, + "event": "on_prompt_end", + "metadata": {}, + "name": "my_template", + "run_id": "", + "tags": ["my_template", "seq:step:1"], + }, + { + "data": { + "input": {"prompts": ["System: You are Cat Agent 007\n" "Human: hello"]} + }, + "event": "on_llm_start", + "metadata": {}, + "name": "FakeStreamingListLLM", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": { + "input": { + "prompts": ["System: You are Cat Agent 007\n" "Human: hello"] + }, + "output": { + "generations": [ + [{"generation_info": None, "text": "abc", "type": "Generation"}] + ], + "llm_output": None, + "run": None, + }, + }, + "event": "on_llm_end", + "metadata": {}, + "name": "FakeStreamingListLLM", + "run_id": "", + "tags": ["seq:step:2"], + }, + { + "data": {"chunk": "a"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": "b"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"chunk": "c"}, + "event": "on_chain_stream", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + { + "data": {"output": "abc"}, + "event": "on_chain_end", + "metadata": {}, + "name": "RunnableSequence", + "run_id": "", + "tags": [], + }, + ] + + +async def test_runnable_each() -> None: + """Test runnable each astream_events.""" + + async def add_one(x: int) -> int: + return x + 1 + + add_one_map = RunnableLambda(add_one).map() # type: ignore + assert await add_one_map.ainvoke([1, 2, 3]) == [2, 3, 4] + + with pytest.raises(NotImplementedError): + async for _ in add_one_map.astream_events([1, 2, 3]): + pass From 0f99646ca6b2295e440447287bd4cbb0175e2d79 Mon Sep 17 00:00:00 2001 From: Ashley Xu <139821907+ashleyxuu@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:34:06 -0800 Subject: [PATCH 102/215] docs: add the enrollment form for`BigQueryVectorSearch` (#16240) This PR adds the enrollment form for BigQueryVectorSearch. --- docs/docs/integrations/platforms/google.mdx | 6 +++++- .../vectorstores/bigquery_vector_search.ipynb | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/docs/integrations/platforms/google.mdx b/docs/docs/integrations/platforms/google.mdx index ddafdb318f55f..5d09b18f56b07 100644 --- a/docs/docs/integrations/platforms/google.mdx +++ b/docs/docs/integrations/platforms/google.mdx @@ -210,7 +210,11 @@ from langchain_community.vectorstores import MatchingEngine > Google BigQuery Vector Search > BigQuery vector search lets you use GoogleSQL to do semantic search, using vector indexes for fast but approximate results, or using brute force for exact results. -> It can calculate Euclidean or Cosine distance. With LangChain, we default to use Euclidean distance. +> It can calculate Euclidean or Cosine distance. With LangChain, we default to use Euclidean distance. + +> This is a private preview (experimental) feature. Please submit this +> [enrollment form](https://docs.google.com/forms/d/18yndSb4dTf2H0orqA9N7NAchQEDQekwWiD5jYfEkGWk/viewform?edit_requested=true) +> if you want to enroll BigQuery Vector Search Experimental. We need to install several python packages. diff --git a/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb b/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb index 29b9430871e8a..af26dcaf8e02b 100644 --- a/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb +++ b/docs/docs/integrations/vectorstores/bigquery_vector_search.ipynb @@ -14,6 +14,15 @@ "This tutorial illustrates how to work with an end-to-end data and embedding management system in LangChain, and provide scalable semantic search in BigQuery." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a **private preview (experimental)** feature. Please submit this\n", + "[enrollment form](https://docs.google.com/forms/d/18yndSb4dTf2H0orqA9N7NAchQEDQekwWiD5jYfEkGWk/viewform?edit_requested=true)\n", + "if you want to enroll BigQuery Vector Search Experimental." + ] + }, { "cell_type": "markdown", "metadata": { From 3613d8a2ad7faee0f3d0a7893179b3eabccebcf9 Mon Sep 17 00:00:00 2001 From: Andreas Motl <andreas.motl@elmyra.de> Date: Fri, 19 Jan 2024 03:35:39 +0100 Subject: [PATCH 103/215] community[patch]: Use SQLAlchemy's `bulk_save_objects` method to improve insert performance (#16244) - **Description:** Improve [pgvector vector store adapter](https://github.com/langchain-ai/langchain/blob/v0.1.1/libs/community/langchain_community/vectorstores/pgvector.py) to save embeddings in batches, to improve its performance. - **Issue:** NA - **Dependencies:** NA - **References:** https://github.com/crate-workbench/langchain/pull/1 Hi again from the CrateDB team, following up on GH-16243, this is another minor patch to the pgvector vector store adapter. Inserting embeddings in batches, using [SQLAlchemy's `bulk_save_objects`](https://docs.sqlalchemy.org/en/20/orm/session_api.html#sqlalchemy.orm.Session.bulk_save_objects) method, can deliver substantial performance gains. With kind regards, Andreas. NB: As I am seeing just now that this method is a legacy feature of SA 2.0, it will need to be reworked on a future iteration. However, it is not deprecated yet, and I haven't been able to come up with a different implementation, yet. --- libs/community/langchain_community/vectorstores/pgvector.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/vectorstores/pgvector.py b/libs/community/langchain_community/vectorstores/pgvector.py index 7fed642c45c95..eff9fac35406b 100644 --- a/libs/community/langchain_community/vectorstores/pgvector.py +++ b/libs/community/langchain_community/vectorstores/pgvector.py @@ -379,6 +379,7 @@ def add_embeddings( collection = self.get_collection(session) if not collection: raise ValueError("Collection not found") + documents = [] for text, metadata, embedding, id in zip(texts, metadatas, embeddings, ids): embedding_store = self.EmbeddingStore( embedding=embedding, @@ -387,7 +388,8 @@ def add_embeddings( custom_id=id, collection_id=collection.uuid, ) - session.add(embedding_store) + documents.append(embedding_store) + session.bulk_save_objects(documents) session.commit() return ids From 9d32af72ce541393fb4700c8ed520137f7c92173 Mon Sep 17 00:00:00 2001 From: mikeFore4 <38678121+mikeFore4@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:44:10 -0500 Subject: [PATCH 104/215] community[patch]: huggingface hub character removal bug fix (#16233) - **Description:** Some text-generation models on huggingface repeat the prompt in their generated response, but not all do! The tests use "gpt2" which DOES repeat the prompt and as such, the HuggingFaceHub class is hardcoded to remove the first few characters of the response (to match the len(prompt)). However, if you are using a model (such as the very popular "meta-llama/Llama-2-7b-chat-hf") that DOES NOT repeat the prompt in it's generated text, then the beginning of the generated text will be cut off. This code change fixes that bug by first checking whether the prompt is repeated in the generated response and removing it conditionally. - **Issue:** #16232 - **Dependencies:** N/A - **Twitter handle:** N/A --- libs/community/langchain_community/llms/huggingface_hub.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/llms/huggingface_hub.py b/libs/community/langchain_community/llms/huggingface_hub.py index 32facc244b0cd..10eb8eb7a0464 100644 --- a/libs/community/langchain_community/llms/huggingface_hub.py +++ b/libs/community/langchain_community/llms/huggingface_hub.py @@ -112,8 +112,10 @@ def _call( if "error" in response: raise ValueError(f"Error raised by inference API: {response['error']}") if self.client.task == "text-generation": - # Text generation return includes the starter text. - text = response[0]["generated_text"][len(prompt) :] + # Text generation sometimes return includes the starter text. + text = response[0]["generated_text"] + if text.startswith(prompt): + text = response[0]["generated_text"][len(prompt) :] elif self.client.task == "text2text-generation": text = response[0]["generated_text"] elif self.client.task == "summarization": From fc84083ce5a5851b22648c5c20824c313fae1a60 Mon Sep 17 00:00:00 2001 From: Tomaz Bratanic <bratanic.tomaz@gmail.com> Date: Fri, 19 Jan 2024 03:45:22 +0100 Subject: [PATCH 105/215] docs: Add neo4j semantic blog post link to templates (#16225) --- templates/neo4j-semantic-layer/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/neo4j-semantic-layer/README.md b/templates/neo4j-semantic-layer/README.md index c2dbe57fe8fa8..7019d627bce12 100644 --- a/templates/neo4j-semantic-layer/README.md +++ b/templates/neo4j-semantic-layer/README.md @@ -2,6 +2,7 @@ This template is designed to implement an agent capable of interacting with a graph database like Neo4j through a semantic layer using OpenAI function calling. The semantic layer equips the agent with a suite of robust tools, allowing it to interact with the graph databas based on the user's intent. +Learn more about the semantic layer template in the [corresponding blog post](https://medium.com/towards-data-science/enhancing-interaction-between-language-models-and-graph-databases-via-a-semantic-layer-0a78ad3eba49). ![Diagram illustrating the workflow of the Neo4j semantic layer with an agent interacting with tools like Information, Recommendation, and Memory, connected to a knowledge graph.](https://raw.githubusercontent.com/langchain-ai/langchain/master/templates/neo4j-semantic-layer/static/workflow.png "Neo4j Semantic Layer Workflow Diagram") From 3ccbe113636ec16925185eca29350856bf3076ff Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Fri, 19 Jan 2024 03:49:02 +0100 Subject: [PATCH 106/215] community[minor]: Add Cassandra document loader (#16215) - **Description:** document loader for Apache Cassandra - **Twitter handle:** cbornet_ --- .../document_loaders/cassandra.py | 123 ++++++++++++++++++ .../tests/integration_tests/.env.example | 7 + .../document_loaders/test_cassandra.py | 121 +++++++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 libs/community/langchain_community/document_loaders/cassandra.py create mode 100644 libs/community/tests/integration_tests/document_loaders/test_cassandra.py diff --git a/libs/community/langchain_community/document_loaders/cassandra.py b/libs/community/langchain_community/document_loaders/cassandra.py new file mode 100644 index 0000000000000..8983f3c56b6a0 --- /dev/null +++ b/libs/community/langchain_community/document_loaders/cassandra.py @@ -0,0 +1,123 @@ +import json +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + Optional, + Sequence, + Union, +) + +from langchain_core.documents import Document + +from langchain_community.document_loaders.base import BaseLoader + + +def default_page_content_mapper(row: Any) -> str: + if hasattr(row, "_asdict"): + return json.dumps(row._asdict()) + return json.dumps(row) + + +_NOT_SET = object() + +if TYPE_CHECKING: + from cassandra.cluster import Session + from cassandra.pool import Host + from cassandra.query import Statement + + +class CassandraLoader(BaseLoader): + def __init__( + self, + table: Optional[str] = None, + session: Optional["Session"] = None, + keyspace: Optional[str] = None, + query: Optional[Union[str, "Statement"]] = None, + page_content_mapper: Callable[[Any], str] = default_page_content_mapper, + metadata_mapper: Callable[[Any], dict] = lambda _: {}, + *, + query_parameters: Union[dict, Sequence] = None, + query_timeout: Optional[float] = _NOT_SET, + query_trace: bool = False, + query_custom_payload: dict = None, + query_execution_profile: Any = _NOT_SET, + query_paging_state: Any = None, + query_host: "Host" = None, + query_execute_as: str = None, + ) -> None: + """ + Document Loader for Apache Cassandra. + + Args: + table: The table to load the data from. + (do not use together with the query parameter) + session: The cassandra driver session. + If not provided, the cassio resolved session will be used. + keyspace: The keyspace of the table. + If not provided, the cassio resolved keyspace will be used. + query: The query used to load the data. + (do not use together with the table parameter) + page_content_mapper: a function to convert a row to string page content. + query_parameters: The query parameters used when calling session.execute . + query_timeout: The query timeout used when calling session.execute . + query_custom_payload: The query custom_payload used when calling + session.execute . + query_execution_profile: The query execution_profile used when calling + session.execute . + query_host: The query host used when calling session.execute . + query_execute_as: The query execute_as used when calling session.execute . + """ + if query and table: + raise ValueError("Cannot specify both query and table.") + + if not query and not table: + raise ValueError("Must specify query or table.") + + if not session or (table and not keyspace): + try: + from cassio.config import check_resolve_keyspace, check_resolve_session + except (ImportError, ModuleNotFoundError): + raise ImportError( + "Could not import a recent cassio package." + "Please install it with `pip install --upgrade cassio`." + ) + + if table: + _keyspace = keyspace or check_resolve_keyspace(keyspace) + self.query = f"SELECT * FROM {_keyspace}.{table};" + self.metadata = {"table": table, "keyspace": _keyspace} + else: + self.query = query + self.metadata = {} + + self.session = session or check_resolve_session(session) + self.page_content_mapper = page_content_mapper + self.metadata_mapper = metadata_mapper + + self.query_kwargs = { + "parameters": query_parameters, + "trace": query_trace, + "custom_payload": query_custom_payload, + "paging_state": query_paging_state, + "host": query_host, + "execute_as": query_execute_as, + } + if query_timeout is not _NOT_SET: + self.query_kwargs["timeout"] = query_timeout + + if query_execution_profile is not _NOT_SET: + self.query_kwargs["execution_profile"] = query_execution_profile + + def load(self) -> List[Document]: + return list(self.lazy_load()) + + def lazy_load(self) -> Iterator[Document]: + for row in self.session.execute(self.query, **self.query_kwargs): + metadata = self.metadata.copy() + metadata.update(self.metadata_mapper(row)) + yield Document( + page_content=self.page_content_mapper(row), metadata=metadata + ) diff --git a/libs/community/tests/integration_tests/.env.example b/libs/community/tests/integration_tests/.env.example index 4ce3040f3434f..99be838353376 100644 --- a/libs/community/tests/integration_tests/.env.example +++ b/libs/community/tests/integration_tests/.env.example @@ -14,6 +14,13 @@ ASTRA_DB_APPLICATION_TOKEN=AstraCS:your_astra_db_application_token # ASTRA_DB_KEYSPACE=your_astra_db_namespace +# cassandra +CASSANDRA_CONTACT_POINTS=127.0.0.1 +# CASSANDRA_USERNAME=your_cassandra_username +# CASSANDRA_PASSWORD=your_cassandra_password +# CASSANDRA_KEYSPACE=your_cassandra_keyspace + + # pinecone # your api key from left menu "API Keys" in https://app.pinecone.io PINECONE_API_KEY=your_pinecone_api_key_here diff --git a/libs/community/tests/integration_tests/document_loaders/test_cassandra.py b/libs/community/tests/integration_tests/document_loaders/test_cassandra.py new file mode 100644 index 0000000000000..59db9f7d3e818 --- /dev/null +++ b/libs/community/tests/integration_tests/document_loaders/test_cassandra.py @@ -0,0 +1,121 @@ +""" +Test of Cassandra document loader class `CassandraLoader` +""" +import os +from typing import Any + +import pytest +from langchain_core.documents import Document + +from langchain_community.document_loaders.cassandra import CassandraLoader + +CASSANDRA_DEFAULT_KEYSPACE = "docloader_test_keyspace" +CASSANDRA_TABLE = "docloader_test_table" + + +@pytest.fixture(autouse=True, scope="session") +def keyspace() -> str: + import cassio + from cassandra.cluster import Cluster + from cassio.config import check_resolve_session, resolve_keyspace + from cassio.table.tables import PlainCassandraTable + + if any( + env_var in os.environ + for env_var in [ + "CASSANDRA_CONTACT_POINTS", + "ASTRA_DB_APPLICATION_TOKEN", + "ASTRA_DB_INIT_STRING", + ] + ): + cassio.init(auto=True) + session = check_resolve_session() + else: + cluster = Cluster() + session = cluster.connect() + keyspace = resolve_keyspace() or CASSANDRA_DEFAULT_KEYSPACE + cassio.init(session=session, keyspace=keyspace) + + session.execute( + ( + f"CREATE KEYSPACE IF NOT EXISTS {keyspace} " + f"WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}}" + ) + ) + + # We use a cassio table by convenience to seed the DB + table = PlainCassandraTable( + table=CASSANDRA_TABLE, keyspace=keyspace, session=session + ) + table.put(row_id="id1", body_blob="text1") + table.put(row_id="id2", body_blob="text2") + + yield keyspace + + session.execute(f"DROP TABLE IF EXISTS {keyspace}.{CASSANDRA_TABLE}") + + +def test_loader_table(keyspace: str) -> None: + loader = CassandraLoader(table=CASSANDRA_TABLE) + assert loader.load() == [ + Document( + page_content='{"row_id": "id1", "body_blob": "text1"}', + metadata={"table": CASSANDRA_TABLE, "keyspace": keyspace}, + ), + Document( + page_content='{"row_id": "id2", "body_blob": "text2"}', + metadata={"table": CASSANDRA_TABLE, "keyspace": keyspace}, + ), + ] + + +def test_loader_query(keyspace: str) -> None: + loader = CassandraLoader( + query=f"SELECT body_blob FROM {keyspace}.{CASSANDRA_TABLE}" + ) + assert loader.load() == [ + Document(page_content='{"body_blob": "text1"}'), + Document(page_content='{"body_blob": "text2"}'), + ] + + +def test_loader_page_content_mapper(keyspace: str) -> None: + def mapper(row: Any) -> str: + return str(row.body_blob) + + loader = CassandraLoader(table=CASSANDRA_TABLE, page_content_mapper=mapper) + assert loader.load() == [ + Document( + page_content="text1", + metadata={"table": CASSANDRA_TABLE, "keyspace": keyspace}, + ), + Document( + page_content="text2", + metadata={"table": CASSANDRA_TABLE, "keyspace": keyspace}, + ), + ] + + +def test_loader_metadata_mapper(keyspace: str) -> None: + def mapper(row: Any) -> dict: + return {"id": row.row_id} + + loader = CassandraLoader(table=CASSANDRA_TABLE, metadata_mapper=mapper) + assert loader.load() == [ + Document( + page_content='{"row_id": "id1", "body_blob": "text1"}', + metadata={ + "table": CASSANDRA_TABLE, + "keyspace": keyspace, + "id": "id1", + }, + ), + Document( + page_content='{"row_id": "id2", "body_blob": "text2"}', + metadata={ + "table": CASSANDRA_TABLE, + "keyspace": keyspace, + "id": "id2", + }, + ), + ] From f63906a9c227df7bd7df68760e0f3a436c46bcc9 Mon Sep 17 00:00:00 2001 From: Lance Martin <122662504+rlancemartin@users.noreply.github.com> Date: Thu, 18 Jan 2024 20:24:35 -0800 Subject: [PATCH 107/215] Test and update MultiON agent toolkit docs (#16235) --- docs/docs/integrations/toolkits/multion.ipynb | 119 +++++++++++++++--- 1 file changed, 104 insertions(+), 15 deletions(-) diff --git a/docs/docs/integrations/toolkits/multion.ipynb b/docs/docs/integrations/toolkits/multion.ipynb index e2b7be448dc52..5a76dc2effbda 100644 --- a/docs/docs/integrations/toolkits/multion.ipynb +++ b/docs/docs/integrations/toolkits/multion.ipynb @@ -5,10 +5,17 @@ "metadata": {}, "source": [ "# MultiOn\n", + " \n", + "[MultiON](https://www.multion.ai/blog/multion-building-a-brighter-future-for-humanity-with-ai-agents) has built an AI Agent that can interact with a broad array of web services and applications. \n", "\n", - "This notebook walks you through connecting LangChain to the `MultiOn` Client in your browser\n", + "This notebook walks you through connecting LangChain to the `MultiOn` Client in your browser. \n", "\n", - "To use this toolkit, you will need to add `MultiOn Extension` to your browser as explained in the [MultiOn for Chrome](https://multion.notion.site/Download-MultiOn-ddddcfe719f94ab182107ca2612c07a5)." + "This enables custom agentic workflow that utilize the power of MultiON agents.\n", + " \n", + "To use this toolkit, you will need to add `MultiOn Extension` to your browser: \n", + "\n", + "* Create a [MultiON account](https://app.multion.ai/login?callbackUrl=%2Fprofile). \n", + "* Add [MultiOn extension for Chrome](https://multion.notion.site/Download-MultiOn-ddddcfe719f94ab182107ca2612c07a5)." ] }, { @@ -22,22 +29,43 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "MultionToolkit()" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from langchain_community.agent_toolkits import MultionToolkit\n", "\n", "toolkit = MultionToolkit()\n", - "\n", "toolkit" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[MultionCreateSession(), MultionUpdateSession(), MultionCloseSession()]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "tools = toolkit.get_tools()\n", "tools" @@ -49,14 +77,24 @@ "source": [ "## MultiOn Setup\n", "\n", + "Once you have created an account, create an API key at https://app.multion.ai/. \n", + "\n", "Login to establish connection with your extension." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged in.\n" + ] + } + ], "source": [ "# Authorize connection to your Browser extention\n", "import multion\n", @@ -68,12 +106,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Use Multion Toolkit within an Agent" + "## Use Multion Toolkit within an Agent\n", + "\n", + "This will use MultiON chrome extension to perform the desired actions.\n", + "\n", + "We can run the below, and view the [trace](https://smith.langchain.com/public/34aaf36d-204a-4ce3-a54e-4a0976f09670/r) to see:\n", + "\n", + "* The agent uses the `create_multion_session` tool\n", + "* It then uses MultiON to execute the query" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": { "tags": [] }, @@ -97,13 +142,57 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mAction:\n", + "```\n", + "{\n", + " \"action\": \"create_multion_session\",\n", + " \"action_input\": {\n", + " \"query\": \"Summarize how AlphaCodium works, a recently released code language model.\",\n", + " \"url\": \"https://www.google.com/search?q=Summarize+how+AlphaCodium+works%2C+a+recently+released+code+language+model.\"\n", + " }\n", + "}\n", + "```\n", + "\n", + "\u001b[0mWARNING: 'new_session' is deprecated and will be removed in a future version. Use 'create_session' instead.\n", + "\n", + "Observation: \u001b[36;1m\u001b[1;3m{'sessionId': '813273951', 'Response': ''}\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I don't know what to do with this sessionId. I'll just ignore it and move on.\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"Final Answer\",\n", + " \"action_input\": \"AlphaCodium is a code language model that was recently released. It is designed to assist developers in writing code by providing suggestions and completing code snippets. It uses machine learning algorithms to analyze code and generate relevant suggestions. It is similar to other code language models such as OpenAI's GPT-3 and Microsoft's IntelliCode.\"\n", + "}\n", + "```\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "\"AlphaCodium is a code language model that was recently released. It is designed to assist developers in writing code by providing suggestions and completing code snippets. It uses machine learning algorithms to analyze code and generate relevant suggestions. It is similar to other code language models such as OpenAI's GPT-3 and Microsoft's IntelliCode.\"" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "agent.run(\"Tweet 'Hi from MultiOn'\")" + "agent.run(\"Summarize how AlphaCodium works, a recently released code language model.\")" ] } ], @@ -123,7 +212,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.9.16" } }, "nbformat": 4, From 021b0484a8d9e8cf0c84bc164fb904202b9e4736 Mon Sep 17 00:00:00 2001 From: Carey <wyr95626@gmail.com> Date: Fri, 19 Jan 2024 15:03:15 +0800 Subject: [PATCH 108/215] community[patch]: add skipped test for inner product normalization (#14989) --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- .../unit_tests/vectorstores/test_faiss.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libs/community/tests/unit_tests/vectorstores/test_faiss.py b/libs/community/tests/unit_tests/vectorstores/test_faiss.py index 8b6cf76751b30..db8228962ca67 100644 --- a/libs/community/tests/unit_tests/vectorstores/test_faiss.py +++ b/libs/community/tests/unit_tests/vectorstores/test_faiss.py @@ -10,6 +10,7 @@ from langchain_community.docstore.base import Docstore from langchain_community.docstore.in_memory import InMemoryDocstore from langchain_community.vectorstores.faiss import FAISS +from langchain_community.vectorstores.utils import DistanceStrategy from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings _PAGE_CONTENT = """This is a page about LangChain. @@ -687,6 +688,26 @@ def test_missing_normalize_score_fn() -> None: faiss_instance.similarity_search_with_relevance_scores("foo", k=2) +@pytest.mark.skip(reason="old relevance score feature") +@pytest.mark.requires("faiss") +def test_ip_score() -> None: + embedding = FakeEmbeddings() + vector = embedding.embed_query("hi") + assert vector == [1] * 9 + [0], f"FakeEmbeddings() has changed, produced {vector}" + + db = FAISS.from_texts( + ["sundays coming so i drive my car"], + embedding=FakeEmbeddings(), + distance_strategy=DistanceStrategy.MAX_INNER_PRODUCT, + ) + scores = db.similarity_search_with_relevance_scores("sundays", k=1) + assert len(scores) == 1, "only one vector should be in db" + _, score = scores[0] + assert ( + score == 1 + ), f"expected inner product of equivalent vectors to be 1, not {score}" + + @pytest.mark.requires("faiss") async def test_async_missing_normalize_score_fn() -> None: """Test doesn't perform similarity search without a valid distance strategy.""" From c0d453d8ac98110ea509eaed4a4a73bcca64f75d Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Fri, 19 Jan 2024 09:34:23 -0500 Subject: [PATCH 109/215] CI: Disable blank issues, add links to QA discussions & show and tell (#16275) Update the issue template --- .github/ISSUE_TEMPLATE/config.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 79a1e6cfeef17..872ee9961da57 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,9 +1,12 @@ -blank_issues_enabled: true +blank_issues_enabled: false version: 2.1 contact_links: - name: 🤔 Question or Problem about: Ask a question or ask about a problem in GitHub Discussions. - url: https://github.com/langchain-ai/langchain/discussions + url: https://www.github.com/langchain-ai/langchain/discussions/categories/q-a - name: Discord url: https://discord.gg/6adMQxSpJS about: General community discussions + - name: Show and tell + about: Show what you built with LangChain + url: https://www.github.com/langchain-ai/langchain/discussions/categories/show-and-tell From 3b649f4331a3fe90294952006e973b135615f097 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Fri, 19 Jan 2024 09:53:51 -0500 Subject: [PATCH 110/215] CI: Add privileged version for issue creation (#16276) Add privileged version for issue creation. This adds a version of issue creation which is unstructured by design to make it easier for maintainers to create issues. Maintainers are expected to write / describe issues clearly. --- .github/ISSUE_TEMPLATE/privileged.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/privileged.yml diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml new file mode 100644 index 0000000000000..d52966efab850 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/privileged.yml @@ -0,0 +1,25 @@ +name: Privileged +description: You are a LangChain maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options. 👇 +body: + - type: markdown + attributes: + value: | + Thanks for your interest in LangChain! 🚀 + + If you are not a LangChain maintainer or were not asked directly by a maintainer to create an issue, then please start the conversation in a [Question in GitHub Discussions](https://github.com/langchain-ai/langchain/discussions/categories/q-a) instead. + + You are a LangChain maintainer if you maintain any of the packages inside of the LangChain repository + or are a regular contributor to LangChain with previous merged merged pull requests. + - type: checkboxes + id: privileged + attributes: + label: Privileged issue + description: Confirm that you are allowed to create an issue here. + options: + - label: I am a LangChain maintainer, or was asked directly by a LangChain maintainer to create an issue here. + required: true + - type: textarea + id: content + attributes: + label: Issue Content + description: Add the content of the issue here. From cc2e30fa13ae00e6e9516a150acfcbaf66d19826 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Fri, 19 Jan 2024 10:13:33 -0500 Subject: [PATCH 111/215] CI: update the description used for privileged issue template (#16277) Update description --- .github/ISSUE_TEMPLATE/privileged.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml index d52966efab850..692a5bde60f7d 100644 --- a/.github/ISSUE_TEMPLATE/privileged.yml +++ b/.github/ISSUE_TEMPLATE/privileged.yml @@ -1,5 +1,5 @@ -name: Privileged -description: You are a LangChain maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options. 👇 +name: 🔒 Privileged +description: You are a LangChain maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options. body: - type: markdown attributes: From 6f7a4149553e09c30a34e75b2476d7d6f2ac124a Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Fri, 19 Jan 2024 08:51:12 -0800 Subject: [PATCH 112/215] docs: fix links (#16284) --- docs/docs/integrations/document_loaders/psychic.ipynb | 2 +- docs/docs/integrations/llms/llm_caching.ipynb | 6 +++--- docs/docs/integrations/providers/activeloop_deeplake.mdx | 2 +- docs/docs/integrations/providers/chroma.mdx | 2 +- docs/docs/integrations/providers/ragatouille.ipynb | 2 +- docs/docs/integrations/retrievers/activeloop.ipynb | 2 +- docs/docs/modules/agents/agent_types/xml_agent.ipynb | 2 +- docs/docs/modules/callbacks/custom_chain.mdx | 6 ------ docs/vercel.json | 4 ---- 9 files changed, 9 insertions(+), 19 deletions(-) delete mode 100644 docs/docs/modules/callbacks/custom_chain.mdx diff --git a/docs/docs/integrations/document_loaders/psychic.ipynb b/docs/docs/integrations/document_loaders/psychic.ipynb index 89e8d6487c23b..edd94d57c048f 100644 --- a/docs/docs/integrations/document_loaders/psychic.ipynb +++ b/docs/docs/integrations/document_loaders/psychic.ipynb @@ -8,7 +8,7 @@ "This notebook covers how to load documents from `Psychic`. See [here](/docs/integrations/providers/psychic) for more details.\n", "\n", "## Prerequisites\n", - "1. Follow the Quick Start section in [this document](/docs/ecosystem/integrations/psychic)\n", + "1. Follow the Quick Start section in [this document](/docs/integrations/providers/psychic)\n", "2. Log into the [Psychic dashboard](https://dashboard.psychic.dev/) and get your secret key\n", "3. Install the frontend react library into your web app and have a user authenticate a connection. The connection will be created using the connection id that you specify." ] diff --git a/docs/docs/integrations/llms/llm_caching.ipynb b/docs/docs/integrations/llms/llm_caching.ipynb index 1bba907163b39..791ff870b0fda 100644 --- a/docs/docs/integrations/llms/llm_caching.ipynb +++ b/docs/docs/integrations/llms/llm_caching.ipynb @@ -318,7 +318,7 @@ "metadata": {}, "source": [ "### Standard Cache\n", - "Use [Redis](/docs/integrations/partners/redis) to cache prompts and responses." + "Use [Redis](/docs/integrations/providers/redis) to cache prompts and responses." ] }, { @@ -404,7 +404,7 @@ "metadata": {}, "source": [ "### Semantic Cache\n", - "Use [Redis](/docs/integrations/partners/redis) to cache prompts and responses and evaluate hits based on semantic similarity." + "Use [Redis](/docs/integrations/providers/redis) to cache prompts and responses and evaluate hits based on semantic similarity." ] }, { @@ -728,7 +728,7 @@ }, "source": [ "## `Momento` Cache\n", - "Use [Momento](/docs/integrations/partners/momento) to cache prompts and responses.\n", + "Use [Momento](/docs/integrations/providers/momento) to cache prompts and responses.\n", "\n", "Requires momento to use, uncomment below to install:" ] diff --git a/docs/docs/integrations/providers/activeloop_deeplake.mdx b/docs/docs/integrations/providers/activeloop_deeplake.mdx index 565eb2132c8d2..121ddfb537fae 100644 --- a/docs/docs/integrations/providers/activeloop_deeplake.mdx +++ b/docs/docs/integrations/providers/activeloop_deeplake.mdx @@ -13,7 +13,7 @@ Activeloop Deep Lake supports SelfQuery Retrieval: ## More Resources 1. [Ultimate Guide to LangChain & Deep Lake: Build ChatGPT to Answer Questions on Your Financial Data](https://www.activeloop.ai/resources/ultimate-guide-to-lang-chain-deep-lake-build-chat-gpt-to-answer-questions-on-your-financial-data/) -2. [Twitter the-algorithm codebase analysis with Deep Lake](/docs/use_cases/question_answering/code/twitter-the-algorithm-analysis-deeplake) +2. [Twitter the-algorithm codebase analysis with Deep Lake](https://github.com/langchain-ai/langchain/blob/master/cookbook/twitter-the-algorithm-analysis-deeplake.ipynb) 3. Here is [whitepaper](https://www.deeplake.ai/whitepaper) and [academic paper](https://arxiv.org/pdf/2209.10785.pdf) for Deep Lake 4. Here is a set of additional resources available for review: [Deep Lake](https://github.com/activeloopai/deeplake), [Get started](https://docs.activeloop.ai/getting-started) and [Tutorials](https://docs.activeloop.ai/hub-tutorials) diff --git a/docs/docs/integrations/providers/chroma.mdx b/docs/docs/integrations/providers/chroma.mdx index 77a914471c774..ab7af6029bd6b 100644 --- a/docs/docs/integrations/providers/chroma.mdx +++ b/docs/docs/integrations/providers/chroma.mdx @@ -18,7 +18,7 @@ whether for semantic search or example selection. from langchain_community.vectorstores import Chroma ``` -For a more detailed walkthrough of the Chroma wrapper, see [this notebook](/docs/integrations/vectorstores/chroma_self_query) +For a more detailed walkthrough of the Chroma wrapper, see [this notebook](/docs/integrations/vectorstores/chroma) ## Retriever diff --git a/docs/docs/integrations/providers/ragatouille.ipynb b/docs/docs/integrations/providers/ragatouille.ipynb index a4089861b4168..46f77ed5a5dea 100644 --- a/docs/docs/integrations/providers/ragatouille.ipynb +++ b/docs/docs/integrations/providers/ragatouille.ipynb @@ -66,7 +66,7 @@ "source": [ "## Document Compressor\n", "\n", - "We can also use RAGatouille off-the-shelf as a reranker. This will allow us to use ColBERT to rerank retrieved results from any generic retriever. The benefits of this are that we can do this on top of any existing index, so that we don't need to create a new idex. We can do this by using the [document compressor](/docs/modules/data_connections/retrievers/contextual_compression) abstraction in LangChain." + "We can also use RAGatouille off-the-shelf as a reranker. This will allow us to use ColBERT to rerank retrieved results from any generic retriever. The benefits of this are that we can do this on top of any existing index, so that we don't need to create a new idex. We can do this by using the [document compressor](/docs/modules/data_connection/retrievers/contextual_compression) abstraction in LangChain." ] }, { diff --git a/docs/docs/integrations/retrievers/activeloop.ipynb b/docs/docs/integrations/retrievers/activeloop.ipynb index e85d4a150ff08..42b71c4a0ca7f 100644 --- a/docs/docs/integrations/retrievers/activeloop.ipynb +++ b/docs/docs/integrations/retrievers/activeloop.ipynb @@ -51,7 +51,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Also you'll need to create a [Activeloop]((https://activeloop.ai/)) account." + "Also you'll need to create a [Activeloop](https://activeloop.ai) account." ] }, { diff --git a/docs/docs/modules/agents/agent_types/xml_agent.ipynb b/docs/docs/modules/agents/agent_types/xml_agent.ipynb index 5ba476a299085..619beba10314a 100644 --- a/docs/docs/modules/agents/agent_types/xml_agent.ipynb +++ b/docs/docs/modules/agents/agent_types/xml_agent.ipynb @@ -23,7 +23,7 @@ "\n", "* Use with regular LLMs, not with chat models.\n", "* Use only with unstructured tools; i.e., tools that accept a single string input.\n", - "* See [AgentTypes](../index) documentation for more agent types.\n", + "* See [AgentTypes](/docs/moduels/agents/agent_types/) documentation for more agent types.\n", ":::" ] }, diff --git a/docs/docs/modules/callbacks/custom_chain.mdx b/docs/docs/modules/callbacks/custom_chain.mdx deleted file mode 100644 index 6ec068eea23e9..0000000000000 --- a/docs/docs/modules/callbacks/custom_chain.mdx +++ /dev/null @@ -1,6 +0,0 @@ -# Callbacks for custom chains - - When you create a custom chain you can easily set it up to use the same callback system as all the built-in chains. -`_call`, `_generate`, `_run`, and equivalent async methods on Chains / LLMs / Chat Models / Agents / Tools now receive a 2nd argument called `run_manager` which is bound to that run, and contains the logging methods that can be used by that object (i.e. `on_llm_new_token`). This is useful when constructing a custom chain. See this guide for more information on how to [create custom chains and use callbacks inside them](/docs/modules/chains/how_to/custom_chain). - - diff --git a/docs/vercel.json b/docs/vercel.json index 8fc8597da84bd..41ae1811d6698 100644 --- a/docs/vercel.json +++ b/docs/vercel.json @@ -380,10 +380,6 @@ "source": "/docs/modules/agents/agents/examples/mrkl_chat(.html?)", "destination": "/docs/modules/agents/" }, - { - "source": "/docs/use_cases(/?)", - "destination": "/docs/use_cases/question_answering/" - }, { "source": "/docs/integrations(/?)", "destination": "/docs/integrations/providers/" From 84bf5787a7036a33745e44de44aa89a074dcec55 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:16:09 -0800 Subject: [PATCH 113/215] core[patch], openai[patch]: Chat openai stream logprobs (#16218) --- .../langchain_core/outputs/chat_generation.py | 10 ++--- .../core/langchain_core/outputs/generation.py | 10 ++--- libs/core/langchain_core/utils/_merge.py | 44 +++++++++++++++++++ .../langchain_openai/chat_models/base.py | 34 +++++++++----- .../chat_models/test_base.py | 34 ++++++++++++++ 5 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 libs/core/langchain_core/utils/_merge.py diff --git a/libs/core/langchain_core/outputs/chat_generation.py b/libs/core/langchain_core/outputs/chat_generation.py index fa5041c34866a..b7bd6042a2c1e 100644 --- a/libs/core/langchain_core/outputs/chat_generation.py +++ b/libs/core/langchain_core/outputs/chat_generation.py @@ -5,6 +5,7 @@ from langchain_core.messages import BaseMessage, BaseMessageChunk from langchain_core.outputs.generation import Generation from langchain_core.pydantic_v1 import root_validator +from langchain_core.utils._merge import merge_dicts class ChatGeneration(Generation): @@ -53,14 +54,13 @@ def get_lc_namespace(cls) -> List[str]: def __add__(self, other: ChatGenerationChunk) -> ChatGenerationChunk: if isinstance(other, ChatGenerationChunk): - generation_info = ( - {**(self.generation_info or {}), **(other.generation_info or {})} - if self.generation_info is not None or other.generation_info is not None - else None + generation_info = merge_dicts( + self.generation_info or {}, + other.generation_info or {}, ) return ChatGenerationChunk( message=self.message + other.message, - generation_info=generation_info, + generation_info=generation_info or None, ) else: raise TypeError( diff --git a/libs/core/langchain_core/outputs/generation.py b/libs/core/langchain_core/outputs/generation.py index 3ede28f9fc677..3f0a79ecb10b0 100644 --- a/libs/core/langchain_core/outputs/generation.py +++ b/libs/core/langchain_core/outputs/generation.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Literal, Optional from langchain_core.load import Serializable +from langchain_core.utils._merge import merge_dicts class Generation(Serializable): @@ -40,14 +41,13 @@ def get_lc_namespace(cls) -> List[str]: def __add__(self, other: GenerationChunk) -> GenerationChunk: if isinstance(other, GenerationChunk): - generation_info = ( - {**(self.generation_info or {}), **(other.generation_info or {})} - if self.generation_info is not None or other.generation_info is not None - else None + generation_info = merge_dicts( + self.generation_info or {}, + other.generation_info or {}, ) return GenerationChunk( text=self.text + other.text, - generation_info=generation_info, + generation_info=generation_info or None, ) else: raise TypeError( diff --git a/libs/core/langchain_core/utils/_merge.py b/libs/core/langchain_core/utils/_merge.py new file mode 100644 index 0000000000000..e21fdd96621fe --- /dev/null +++ b/libs/core/langchain_core/utils/_merge.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import Any, Dict + + +def merge_dicts(left: Dict[str, Any], right: Dict[str, Any]) -> Dict[str, Any]: + """Merge two dicts, handling specific scenarios where a key exists in both + dictionaries but has a value of None in 'left'. In such cases, the method uses the + value from 'right' for that key in the merged dictionary. + + Example: + If left = {"function_call": {"arguments": None}} and + right = {"function_call": {"arguments": "{\n"}} + then, after merging, for the key "function_call", + the value from 'right' is used, + resulting in merged = {"function_call": {"arguments": "{\n"}}. + """ + merged = left.copy() + for k, v in right.items(): + if k not in merged: + merged[k] = v + elif merged[k] is None and v: + merged[k] = v + elif v is None: + continue + elif merged[k] == v: + continue + elif type(merged[k]) != type(v): + raise TypeError( + f'additional_kwargs["{k}"] already exists in this message,' + " but with a different type." + ) + elif isinstance(merged[k], str): + merged[k] += v + elif isinstance(merged[k], dict): + merged[k] = merge_dicts(merged[k], v) + elif isinstance(merged[k], list): + merged[k] = merged[k] + v + else: + raise TypeError( + f"Additional kwargs key {k} already exists in left dict and value has " + f"unsupported type {type(merged[k])}." + ) + return merged diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 06bd22485cf2d..10d1dadf22ead 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -404,15 +404,19 @@ def _stream( chunk = _convert_delta_to_message_chunk( choice["delta"], default_chunk_class ) - finish_reason = choice.get("finish_reason") - generation_info = ( - dict(finish_reason=finish_reason) if finish_reason is not None else None - ) + generation_info = {} + if finish_reason := choice.get("finish_reason"): + generation_info["finish_reason"] = finish_reason + logprobs = choice.get("logprobs") + if logprobs: + generation_info["logprobs"] = logprobs default_chunk_class = chunk.__class__ - chunk = ChatGenerationChunk(message=chunk, generation_info=generation_info) + chunk = ChatGenerationChunk( + message=chunk, generation_info=generation_info or None + ) yield chunk if run_manager: - run_manager.on_llm_new_token(chunk.text, chunk=chunk) + run_manager.on_llm_new_token(chunk.text, chunk=chunk, logprobs=logprobs) def _generate( self, @@ -492,15 +496,21 @@ async def _astream( chunk = _convert_delta_to_message_chunk( choice["delta"], default_chunk_class ) - finish_reason = choice.get("finish_reason") - generation_info = ( - dict(finish_reason=finish_reason) if finish_reason is not None else None - ) + generation_info = {} + if finish_reason := choice.get("finish_reason"): + generation_info["finish_reason"] = finish_reason + logprobs = choice.get("logprobs") + if logprobs: + generation_info["logprobs"] = logprobs default_chunk_class = chunk.__class__ - chunk = ChatGenerationChunk(message=chunk, generation_info=generation_info) + chunk = ChatGenerationChunk( + message=chunk, generation_info=generation_info or None + ) yield chunk if run_manager: - await run_manager.on_llm_new_token(token=chunk.text, chunk=chunk) + await run_manager.on_llm_new_token( + token=chunk.text, chunk=chunk, logprobs=logprobs + ) async def _agenerate( self, 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 c86112891f139..33006a624cf32 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 @@ -391,3 +391,37 @@ def test_invoke() -> None: result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"])) assert isinstance(result.content, str) + + +def test_logprobs() -> None: + llm = ChatOpenAI() + result = llm.generate([[HumanMessage(content="I'm PickleRick")]], logprobs=True) + assert result.generations[0][0].generation_info + assert "content" in result.generations[0][0].generation_info["logprobs"] + + +async def test_async_logprobs() -> None: + llm = ChatOpenAI() + result = await llm.agenerate( + [[HumanMessage(content="I'm PickleRick")]], logprobs=True + ) + assert result.generations[0][0].generation_info + assert "content" in result.generations[0][0].generation_info["logprobs"] + + +def test_logprobs_streaming() -> None: + llm = ChatOpenAI() + result = llm.generate( + [[HumanMessage(content="I'm PickleRick")]], logprobs=True, stream=True + ) + assert result.generations[0][0].generation_info + assert "content" in result.generations[0][0].generation_info["logprobs"] + + +async def test_async_logprobs_streaming() -> None: + llm = ChatOpenAI() + result = await llm.agenerate( + [[HumanMessage(content="I'm PickleRick")]], logprobs=True, stream=True + ) + assert result.generations[0][0].generation_info + assert "content" in result.generations[0][0].generation_info["logprobs"] From 2454fefc5337fab8f2585f8ae073c7aa42540357 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:19:22 -0800 Subject: [PATCH 114/215] docs: agent prompt docs (#16105) --- libs/langchain/langchain/agents/json_chat/base.py | 14 +++++++++----- .../agents/openai_functions_agent/base.py | 11 ++++++++--- .../langchain/agents/openai_tools/base.py | 12 +++++++++--- libs/langchain/langchain/agents/react/agent.py | 15 +++++++++------ .../langchain/agents/self_ask_with_search/base.py | 7 ++++++- .../langchain/agents/structured_chat/base.py | 14 +++++++++----- libs/langchain/langchain/agents/xml/base.py | 8 +++++++- 7 files changed, 57 insertions(+), 24 deletions(-) diff --git a/libs/langchain/langchain/agents/json_chat/base.py b/libs/langchain/langchain/agents/json_chat/base.py index 3e34568a9721b..ffabf68921b0b 100644 --- a/libs/langchain/langchain/agents/json_chat/base.py +++ b/libs/langchain/langchain/agents/json_chat/base.py @@ -19,10 +19,7 @@ def create_json_chat_agent( Args: llm: LLM to use as the agent. tools: Tools this agent has access to. - prompt: The prompt to use, must have input keys: - `tools`: contains descriptions and arguments for each tool. - `tool_names`: contains all tool names. - `agent_scratchpad`: contains previous agent actions and tool outputs. + prompt: The prompt to use. See Prompt section below for more. Returns: A Runnable sequence representing an agent. It takes as input all the same input @@ -58,7 +55,14 @@ def create_json_chat_agent( } ) - Creating prompt example: + Prompt: + + The prompt must have input keys: + * `tools`: contains descriptions and arguments for each tool. + * `tool_names`: contains all tool names. + * `agent_scratchpad`: must be a MessagesPlaceholder. Contains previous agent actions and tool outputs as messages. + + Here's an example: .. code-block:: python diff --git a/libs/langchain/langchain/agents/openai_functions_agent/base.py b/libs/langchain/langchain/agents/openai_functions_agent/base.py index e0180693202e5..633d7a27cd697 100644 --- a/libs/langchain/langchain/agents/openai_functions_agent/base.py +++ b/libs/langchain/langchain/agents/openai_functions_agent/base.py @@ -239,8 +239,7 @@ def create_openai_functions_agent( so either be an OpenAI model that supports that or a wrapper of a different model that adds in equivalent support. tools: Tools this agent has access to. - prompt: The prompt to use, must have input key `agent_scratchpad`, which will - contain agent action and tool output messages. + prompt: The prompt to use. See Prompt section below for more. Returns: A Runnable sequence representing an agent. It takes as input all the same input @@ -278,7 +277,13 @@ def create_openai_functions_agent( } ) - Creating prompt example: + Prompt: + + The agent prompt must have an `agent_scratchpad` key that is a + ``MessagesPlaceholder``. Intermediate agent actions and tool output + messages will be passed in here. + + Here's an example: .. code-block:: python diff --git a/libs/langchain/langchain/agents/openai_tools/base.py b/libs/langchain/langchain/agents/openai_tools/base.py index 4395fb32bbd2d..e76ccd994ef5c 100644 --- a/libs/langchain/langchain/agents/openai_tools/base.py +++ b/libs/langchain/langchain/agents/openai_tools/base.py @@ -20,8 +20,8 @@ def create_openai_tools_agent( Args: llm: LLM to use as the agent. tools: Tools this agent has access to. - prompt: The prompt to use, must have input key `agent_scratchpad`, which will - contain agent action and tool output messages. + prompt: The prompt to use. See Prompt section below for more on the expected + input variables. Returns: A Runnable sequence representing an agent. It takes as input all the same input @@ -57,7 +57,13 @@ def create_openai_tools_agent( } ) - Creating prompt example: + Prompt: + + The agent prompt must have an `agent_scratchpad` key that is a + ``MessagesPlaceholder``. Intermediate agent actions and tool output + messages will be passed in here. + + Here's an example: .. code-block:: python diff --git a/libs/langchain/langchain/agents/react/agent.py b/libs/langchain/langchain/agents/react/agent.py index 23277c36540db..93709fca65bbd 100644 --- a/libs/langchain/langchain/agents/react/agent.py +++ b/libs/langchain/langchain/agents/react/agent.py @@ -20,11 +20,7 @@ def create_react_agent( Args: llm: LLM to use as the agent. tools: Tools this agent has access to. - prompt: The prompt to use, must have input keys: - `tools`: contains descriptions and arguments for each tool. - `tool_names`: contains all tool names. - `agent_scratchpad`: contains previous agent actions and tool outputs. - + prompt: The prompt to use. See Prompt section below for more. Returns: A Runnable sequence representing an agent. It takes as input all the same input @@ -59,7 +55,14 @@ def create_react_agent( } ) - Creating prompt example: + Prompt: + + The prompt must have input keys: + * `tools`: contains descriptions and arguments for each tool. + * `tool_names`: contains all tool names. + * `agent_scratchpad`: contains previous agent actions and tool outputs as a string. + + Here's an example: .. code-block:: python diff --git a/libs/langchain/langchain/agents/self_ask_with_search/base.py b/libs/langchain/langchain/agents/self_ask_with_search/base.py index d7526f4c5486f..26447f0239a7a 100644 --- a/libs/langchain/langchain/agents/self_ask_with_search/base.py +++ b/libs/langchain/langchain/agents/self_ask_with_search/base.py @@ -121,7 +121,12 @@ def create_self_ask_with_search_agent( agent_executor.invoke({"input": "hi"}) - Create prompt example: + Prompt: + + The prompt must have input key `agent_scratchpad` which will + contain agent actions and tool outputs as a string. + + Here's an example: .. code-block:: python diff --git a/libs/langchain/langchain/agents/structured_chat/base.py b/libs/langchain/langchain/agents/structured_chat/base.py index ef037860d3c46..3dc29b754bd05 100644 --- a/libs/langchain/langchain/agents/structured_chat/base.py +++ b/libs/langchain/langchain/agents/structured_chat/base.py @@ -158,10 +158,7 @@ def create_structured_chat_agent( Args: llm: LLM to use as the agent. tools: Tools this agent has access to. - prompt: The prompt to use, must have input keys - `tools`: contains descriptions and arguments for each tool. - `tool_names`: contains all tool names. - `agent_scratchpad`: contains previous agent actions and tool outputs. + prompt: The prompt to use. See Prompt section below for more. Returns: A Runnable sequence representing an agent. It takes as input all the same input @@ -197,7 +194,14 @@ def create_structured_chat_agent( } ) - Creating prompt example: + Prompt: + + The prompt must have input keys: + * `tools`: contains descriptions and arguments for each tool. + * `tool_names`: contains all tool names. + * `agent_scratchpad`: contains previous agent actions and tool outputs as a string. + + Here's an example: .. code-block:: python diff --git a/libs/langchain/langchain/agents/xml/base.py b/libs/langchain/langchain/agents/xml/base.py index be85a7b9738ff..fe3f4883ece5a 100644 --- a/libs/langchain/langchain/agents/xml/base.py +++ b/libs/langchain/langchain/agents/xml/base.py @@ -152,7 +152,13 @@ def create_xml_agent( } ) - Creating prompt example: + Prompt: + + The prompt must have input keys: + * `tools`: contains descriptions for each tool. + * `agent_scratchpad`: contains previous agent actions and tool outputs as an XML string. + + Here's an example: .. code-block:: python From e3828bee43c023f7f06fce6783c5eb0553d2844d Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:28:31 -0800 Subject: [PATCH 115/215] core[patch]: Release 0.1.13 (#16287) --- libs/core/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 993c8183c830e..c2860cf974114 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-core" -version = "0.1.12" +version = "0.1.13" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From 881d1c3ec5564f0790129ba5fd86508dae00d0c8 Mon Sep 17 00:00:00 2001 From: Lance Martin <122662504+rlancemartin@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:37:20 -0800 Subject: [PATCH 116/215] Update MultiON toolkit docs (#16286) --- docs/docs/integrations/toolkits/multion.ipynb | 119 +++++++++--------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/docs/docs/integrations/toolkits/multion.ipynb b/docs/docs/integrations/toolkits/multion.ipynb index 5a76dc2effbda..488ac7d149023 100644 --- a/docs/docs/integrations/toolkits/multion.ipynb +++ b/docs/docs/integrations/toolkits/multion.ipynb @@ -21,7 +21,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ "%pip install --upgrade --quiet multion langchain -q" @@ -29,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 37, "metadata": {}, "outputs": [ { @@ -38,7 +40,7 @@ "MultionToolkit()" ] }, - "execution_count": 7, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -52,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -61,7 +63,7 @@ "[MultionCreateSession(), MultionUpdateSession(), MultionCloseSession()]" ] }, - "execution_count": 8, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -84,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 39, "metadata": {}, "outputs": [ { @@ -118,81 +120,86 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "tags": [] - }, + "execution_count": 40, + "metadata": {}, "outputs": [], "source": [ - "from langchain.agents import AgentType, initialize_agent\n", - "from langchain_openai import OpenAI\n", - "\n", - "llm = OpenAI(temperature=0)\n", - "from langchain_community.agent_toolkits import MultionToolkit\n", - "\n", - "toolkit = MultionToolkit()\n", - "tools = toolkit.get_tools()\n", - "agent = initialize_agent(\n", + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain_openai import ChatOpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "# Prompt\n", + "instructions = \"\"\"You are an assistant.\"\"\"\n", + "base_prompt = hub.pull(\"langchain-ai/openai-functions-template\")\n", + "prompt = base_prompt.partial(instructions=instructions)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "# LLM\n", + "llm = ChatOpenAI(temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "# Agent\n", + "agent = create_openai_functions_agent(llm, toolkit.get_tools(), prompt)\n", + "agent_executor = AgentExecutor(\n", + " agent=agent,\n", " tools=toolkit.get_tools(),\n", - " llm=llm,\n", - " agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,\n", - " verbose=True,\n", + " verbose=False,\n", ")" ] }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "tags": [] - }, + "execution_count": 46, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction:\n", - "```\n", - "{\n", - " \"action\": \"create_multion_session\",\n", - " \"action_input\": {\n", - " \"query\": \"Summarize how AlphaCodium works, a recently released code language model.\",\n", - " \"url\": \"https://www.google.com/search?q=Summarize+how+AlphaCodium+works%2C+a+recently+released+code+language+model.\"\n", - " }\n", - "}\n", - "```\n", - "\n", - "\u001b[0mWARNING: 'new_session' is deprecated and will be removed in a future version. Use 'create_session' instead.\n", - "\n", - "Observation: \u001b[36;1m\u001b[1;3m{'sessionId': '813273951', 'Response': ''}\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I don't know what to do with this sessionId. I'll just ignore it and move on.\n", - "Action:\n", - "```\n", - "{\n", - " \"action\": \"Final Answer\",\n", - " \"action_input\": \"AlphaCodium is a code language model that was recently released. It is designed to assist developers in writing code by providing suggestions and completing code snippets. It uses machine learning algorithms to analyze code and generate relevant suggestions. It is similar to other code language models such as OpenAI's GPT-3 and Microsoft's IntelliCode.\"\n", - "}\n", - "```\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" + "WARNING: 'new_session' is deprecated and will be removed in a future version. Use 'create_session' instead.\n", + "WARNING: 'update_session' is deprecated and will be removed in a future version. Use 'step_session' instead.\n", + "WARNING: 'update_session' is deprecated and will be removed in a future version. Use 'step_session' instead.\n", + "WARNING: 'update_session' is deprecated and will be removed in a future version. Use 'step_session' instead.\n", + "WARNING: 'update_session' is deprecated and will be removed in a future version. Use 'step_session' instead.\n" ] }, { "data": { "text/plain": [ - "\"AlphaCodium is a code language model that was recently released. It is designed to assist developers in writing code by providing suggestions and completing code snippets. It uses machine learning algorithms to analyze code and generate relevant suggestions. It is similar to other code language models such as OpenAI's GPT-3 and Microsoft's IntelliCode.\"" + "{'input': 'Use multion to how AlphaCodium works, a recently released code language model.',\n", + " 'output': 'AlphaCodium is a recently released code language model that is designed to assist developers in writing code more efficiently. It is based on advanced machine learning techniques and natural language processing. AlphaCodium can understand and generate code in multiple programming languages, making it a versatile tool for developers.\\n\\nThe model is trained on a large dataset of code snippets and programming examples, allowing it to learn patterns and best practices in coding. It can provide suggestions and auto-complete code based on the context and the desired outcome.\\n\\nAlphaCodium also has the ability to analyze code and identify potential errors or bugs. It can offer recommendations for improving code quality and performance.\\n\\nOverall, AlphaCodium aims to enhance the coding experience by providing intelligent assistance and reducing the time and effort required to write high-quality code.\\n\\nFor more detailed information, you can visit the official AlphaCodium website or refer to the documentation and resources available online.\\n\\nI hope this helps! Let me know if you have any other questions.'}" ] }, - "execution_count": 11, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "agent.run(\"Summarize how AlphaCodium works, a recently released code language model.\")" + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"Use multion to explain how AlphaCodium works, a recently released code language model.\"\n", + " }\n", + ")" ] } ], From 63e2acc9647ac8879a608be6df9049d235bfbe28 Mon Sep 17 00:00:00 2001 From: Sagar B Manjunath <sbmbms@gmail.com> Date: Fri, 19 Jan 2024 23:14:08 +0530 Subject: [PATCH 117/215] docs: Fix minor issues in NVIDIA RAG canonical template (#16189) - **Description:** Fixes a few issues in NVIDIAcanonical RAG template's README, and adds a notebook for the template - **Dependencies:** Adds the pypdf dependency which is needed for ingestion, and updates the lock file --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- templates/nvidia-rag-canonical/README.md | 12 ++--- .../nvidia_rag_canonical.ipynb | 52 +++++++++++++++++++ templates/nvidia-rag-canonical/poetry.lock | 35 ++++++++++++- templates/nvidia-rag-canonical/pyproject.toml | 1 + 4 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 templates/nvidia-rag-canonical/nvidia_rag_canonical.ipynb diff --git a/templates/nvidia-rag-canonical/README.md b/templates/nvidia-rag-canonical/README.md index 8ad465813433a..6e095a4749625 100644 --- a/templates/nvidia-rag-canonical/README.md +++ b/templates/nvidia-rag-canonical/README.md @@ -45,16 +45,16 @@ langchain app add nvidia-rag-canonical And add the following code to your `server.py` file: ```python -from nvidia_rag_canonical import chain as rag_nvidia_chain +from nvidia_rag_canonical import chain as nvidia_rag_canonical_chain -add_routes(app, rag_nvidia_chain, path="/nvidia-rag") +add_routes(app, nvidia_rag_canonical_chain, path="/nvidia-rag-canonical") ``` If you want to set up an ingestion pipeline, you can add the following code to your `server.py` file: ```python -from rag_nvidia_canonical import ingest as rag_nvidia_ingest +from nvidia_rag_canonical import ingest as nvidia_rag_ingest -add_routes(app, rag_nvidia_ingest, path="/nvidia-rag-ingest") +add_routes(app, nvidia_rag_ingest, path="/nvidia-rag-ingest") ``` Note that for files ingested by the ingestion API, the server will need to be restarted for the newly ingested files to be accessible by the retriever. @@ -84,14 +84,14 @@ This will start the FastAPI app with a server is running locally at [http://localhost:8000](http://localhost:8000) We can see all templates at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) -We can access the playground at [http://127.0.0.1:8000/nvidia-rag/playground](http://127.0.0.1:8000/nvidia-rag/playground) +We can access the playground at [http://127.0.0.1:8000/nvidia-rag-canonical/playground](http://127.0.0.1:8000/nvidia-rag-canonical/playground) We can access the template from code with: ```python from langserve.client import RemoteRunnable -runnable = RemoteRunnable("http://localhost:8000/nvidia-rag") +runnable = RemoteRunnable("http://localhost:8000/nvidia-rag-canonical") ``` diff --git a/templates/nvidia-rag-canonical/nvidia_rag_canonical.ipynb b/templates/nvidia-rag-canonical/nvidia_rag_canonical.ipynb new file mode 100644 index 0000000000000..eaff8b2e6a6d0 --- /dev/null +++ b/templates/nvidia-rag-canonical/nvidia_rag_canonical.ipynb @@ -0,0 +1,52 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "681a5d1e", + "metadata": {}, + "source": [ + "## Connect to template\n", + "\n", + "In `server.py`, set -\n", + "```\n", + "add_routes(app, nvidia_rag_canonical_chain, path=\"/nvidia_rag_canonical\")\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d774be2a", + "metadata": {}, + "outputs": [], + "source": [ + "from langserve.client import RemoteRunnable\n", + "\n", + "rag_app = RemoteRunnable(\"http://0.0.0.0:8000/nvidia_rag_canonical\")\n", + "rag_app.invoke(\"How many Americans receive Social Security Benefits?\")" + ] + } + ], + "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": 5 +} diff --git a/templates/nvidia-rag-canonical/poetry.lock b/templates/nvidia-rag-canonical/poetry.lock index f3c44dd28be40..de556e11e1467 100644 --- a/templates/nvidia-rag-canonical/poetry.lock +++ b/templates/nvidia-rag-canonical/poetry.lock @@ -1337,8 +1337,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -1660,6 +1660,27 @@ pyarrow = ">=12.0.0" requests = "*" ujson = ">=2.0.0" +[[package]] +name = "pypdf" +version = "3.17.4" +description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pypdf-3.17.4-py3-none-any.whl", hash = "sha256:6aa0f61b33779b64486de3f42835d3668badd48dac4a536aeb87da187a5eacd2"}, + {file = "pypdf-3.17.4.tar.gz", hash = "sha256:ec96e2e4fc9648ac609d19c00d41e9d606e0ae2ce5a0bbe7691426f5f157166a"}, +] + +[package.dependencies] +typing_extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} + +[package.extras] +crypto = ["PyCryptodome", "cryptography"] +dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"] +docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] +full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"] +image = ["Pillow (>=8.0.0)"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1711,6 +1732,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1718,8 +1740,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1736,6 +1765,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1743,6 +1773,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2255,4 +2286,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "f5c0f88fe09baa6cb1ea4cf9d615e98efa3081fbccfc03688f1e2fbcf42273dd" +content-hash = "aa7dec65825e265779c9f00b10ffe8f34c96b25e8385e9e5235b89c411345a00" diff --git a/templates/nvidia-rag-canonical/pyproject.toml b/templates/nvidia-rag-canonical/pyproject.toml index 8e9b8944320c7..b444a706f6a04 100644 --- a/templates/nvidia-rag-canonical/pyproject.toml +++ b/templates/nvidia-rag-canonical/pyproject.toml @@ -10,6 +10,7 @@ python = ">=3.8.1,<4.0" langchain = "^0.1" pymilvus = ">=2.3.0" langchain-nvidia-aiplay = "^0.0.2" +pypdf = ">=3.1" [tool.poetry.group.dev.dependencies] langchain-cli = ">=0.0.20" From 9b0a531aa2b798315f602620101f8b6e774e2c03 Mon Sep 17 00:00:00 2001 From: Hongyu Lin <67950264+hon-gyu@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:44:22 +0000 Subject: [PATCH 118/215] doc: Fix small typo in quickstart (#16164) - **Description:** fix small typo in quickstart --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- docs/docs/get_started/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/get_started/quickstart.mdx b/docs/docs/get_started/quickstart.mdx index 4c5462ead82d2..76b402138ef25 100644 --- a/docs/docs/get_started/quickstart.mdx +++ b/docs/docs/get_started/quickstart.mdx @@ -59,7 +59,7 @@ In this quickstart, we will walk through a few different ways of doing that. We will start with a simple LLM chain, which just relies on information in the prompt template to respond. Next, we will build a retrieval chain, which fetches data from a separate database and passes that into the prompt template. We will then add in chat history, to create a conversation retrieval chain. This allows you interact in a chat manner with this LLM, so it remembers previous questions. -Finally, we will build an agent - which utilizes and LLM to determine whether or not it needs to fetch data to answer questions. +Finally, we will build an agent - which utilizes an LLM to determine whether or not it needs to fetch data to answer questions. We will cover these at a high level, but there are lot of details to all of these! We will link to relevant docs. From 39b3c6d94c7f4311aae2d3c79d0fded39dd05938 Mon Sep 17 00:00:00 2001 From: Hamza Kyamanywa <untilhamza@gmail.com> Date: Sat, 20 Jan 2024 02:44:56 +0900 Subject: [PATCH 119/215] langchain[patch]: Add konlpy based text splitting for Korean (#16003) - **Description:** Adds a text splitter based on [Konlpy](https://konlpy.org/en/latest/#start) which is a Python package for natural language processing (NLP) of the Korean language. (It is like Spacy or NLTK for Korean) - **Dependencies:** Konlpy would have to be installed before this splitter is used, - **Twitter handle:** @untilhamza --- .../split_by_token.ipynb | 101 +++++++++++++++++- libs/langchain/langchain/text_splitter.py | 31 ++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/docs/docs/modules/data_connection/document_transformers/split_by_token.ipynb b/docs/docs/modules/data_connection/document_transformers/split_by_token.ipynb index f0c36d0022e66..4236d0e38edbd 100644 --- a/docs/docs/modules/data_connection/document_transformers/split_by_token.ipynb +++ b/docs/docs/modules/data_connection/document_transformers/split_by_token.ipynb @@ -419,6 +419,105 @@ "print(texts[0])" ] }, + { + "cell_type": "markdown", + "id": "98a3f975", + "metadata": {}, + "source": [ + "## KoNLPY\n", + "> [KoNLPy: Korean NLP in Python](https://konlpy.org/en/latest/) is is a Python package for natural language processing (NLP) of the Korean language.\n", + "\n", + "Token splitting involves the segmentation of text into smaller, more manageable units called tokens. These tokens are often words, phrases, symbols, or other meaningful elements crucial for further processing and analysis. In languages like English, token splitting typically involves separating words by spaces and punctuation marks. The effectiveness of token splitting largely depends on the tokenizer's understanding of the language structure, ensuring the generation of meaningful tokens. Since tokenizers designed for the English language are not equipped to understand the unique semantic structures of other languages, such as Korean, they cannot be effectively used for Korean language processing.\n", + "\n", + "### Token splitting for Korean with KoNLPy's Kkma Analyzer\n", + "In case of Korean text, KoNLPY includes at morphological analyzer called `Kkma` (Korean Knowledge Morpheme Analyzer). `Kkma` provides detailed morphological analysis of Korean text. It breaks down sentences into words and words into their respective morphemes, identifying parts of speech for each token. It can segment a block of text into individual sentences, which is particularly useful for processing long texts.\n", + "\n", + "### Usage Considerations\n", + "While `Kkma` is renowned for its detailed analysis, it is important to note that this precision may impact processing speed. Thus, `Kkma` is best suited for applications where analytical depth is prioritized over rapid text processing." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "88ec8f2f", + "metadata": {}, + "outputs": [], + "source": [ + "# pip install konlpy" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "ddfba6cf", + "metadata": {}, + "outputs": [], + "source": [ + "# This is a long Korean document that we want to split up into its component sentences.\n", + "with open(\"./your_korean_doc.txt\") as f:\n", + " korean_document = f.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "225dfc5c", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.text_splitter import KonlpyTextSplitter\n", + "\n", + "text_splitter = KonlpyTextSplitter()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "cf156711", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "춘향전 옛날에 남원에 이 도령이라는 벼슬아치 아들이 있었다.\n", + "\n", + "그의 외모는 빛나는 달처럼 잘생겼고, 그의 학식과 기예는 남보다 뛰어났다.\n", + "\n", + "한편, 이 마을에는 춘향이라는 절세 가인이 살고 있었다.\n", + "\n", + "춘 향의 아름다움은 꽃과 같아 마을 사람들 로부터 많은 사랑을 받았다.\n", + "\n", + "어느 봄날, 도령은 친구들과 놀러 나갔다가 춘 향을 만 나 첫 눈에 반하고 말았다.\n", + "\n", + "두 사람은 서로 사랑하게 되었고, 이내 비밀스러운 사랑의 맹세를 나누었다.\n", + "\n", + "하지만 좋은 날들은 오래가지 않았다.\n", + "\n", + "도령의 아버지가 다른 곳으로 전근을 가게 되어 도령도 떠나 야만 했다.\n", + "\n", + "이별의 아픔 속에서도, 두 사람은 재회를 기약하며 서로를 믿고 기다리기로 했다.\n", + "\n", + "그러나 새로 부임한 관아의 사또가 춘 향의 아름다움에 욕심을 내 어 그녀에게 강요를 시작했다.\n", + "\n", + "춘 향 은 도령에 대한 자신의 사랑을 지키기 위해, 사또의 요구를 단호히 거절했다.\n", + "\n", + "이에 분노한 사또는 춘 향을 감옥에 가두고 혹독한 형벌을 내렸다.\n", + "\n", + "이야기는 이 도령이 고위 관직에 오른 후, 춘 향을 구해 내는 것으로 끝난다.\n", + "\n", + "두 사람은 오랜 시련 끝에 다시 만나게 되고, 그들의 사랑은 온 세상에 전해 지며 후세에까지 이어진다.\n", + "\n", + "- 춘향전 (The Tale of Chunhyang)\n" + ] + } + ], + "source": [ + "texts = text_splitter.split_text(korean_document)\n", + "# The sentences are split with \"\\n\\n\" characters.\n", + "print(texts[0])" + ] + }, { "cell_type": "markdown", "id": "13dc0983", @@ -521,7 +620,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.10.12" }, "vscode": { "interpreter": { diff --git a/libs/langchain/langchain/text_splitter.py b/libs/langchain/langchain/text_splitter.py index da65a80dc9fd7..cd6204adfc8b6 100644 --- a/libs/langchain/langchain/text_splitter.py +++ b/libs/langchain/langchain/text_splitter.py @@ -1427,6 +1427,37 @@ def split_text(self, text: str) -> List[str]: return self._merge_splits(splits, self._separator) +class KonlpyTextSplitter(TextSplitter): + """Splitting text using Konlpy package. + + It is good for splitting Korean text. + """ + + def __init__( + self, + separator: str = "\n\n", + **kwargs: Any, + ) -> None: + """Initialize the Konlpy text splitter.""" + super().__init__(**kwargs) + self._separator = separator + try: + from konlpy.tag import Kkma + except ImportError: + raise ImportError( + """ + Konlpy is not installed, please install it with + `pip install konlpy` + """ + ) + self.kkma = Kkma() + + def split_text(self, text: str) -> List[str]: + """Split incoming text and return chunks.""" + splits = self.kkma.sentences(text) + return self._merge_splits(splits, self._separator) + + # For backwards compatibility class PythonCodeTextSplitter(RecursiveCharacterTextSplitter): """Attempts to split the text along Python syntax.""" From 91230ef5d1ca4c1c3a8df67506e23c3a150b0fae Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:15:08 -0800 Subject: [PATCH 120/215] openai[patch]: Release 0.0.3 (#16289) --- libs/partners/openai/poetry.lock | 24 +++++++++++++++++------- libs/partners/openai/pyproject.toml | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/libs/partners/openai/poetry.lock b/libs/partners/openai/poetry.lock index 8f4681543466e..465391d040454 100644 --- a/libs/partners/openai/poetry.lock +++ b/libs/partners/openai/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -318,7 +318,7 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.7" +version = "0.1.13" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -328,7 +328,7 @@ develop = true [package.dependencies] anyio = ">=3,<5" jsonpatch = "^1.33" -langsmith = "~0.0.63" +langsmith = "^0.0.83" packaging = "^23.2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -344,13 +344,13 @@ url = "../../core" [[package]] name = "langsmith" -version = "0.0.75" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.75-py3-none-any.whl", hash = "sha256:3e008854204c5eaae007f34c7e249059218605689c385c037f6a40cac044833b"}, - {file = "langsmith-0.0.75.tar.gz", hash = "sha256:3fd44c58bd53cb9366af3de129c7f11b6947914f1bb598a585240df0e2c566eb"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -738,6 +738,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -745,8 +746,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -763,6 +771,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -770,6 +779,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1137,4 +1147,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "19354033ba1c0b24094244fb6429e814dea20bdfde4dc67254710cbb7f410d50" +content-hash = "864d5c8b19403aae2a5658e042d4c0deb64fb9ce89b2bd3e751b5c0a1bf8dc68" diff --git a/libs/partners/openai/pyproject.toml b/libs/partners/openai/pyproject.toml index 6bdf5a1f62be3..a6f419780037b 100644 --- a/libs/partners/openai/pyproject.toml +++ b/libs/partners/openai/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-openai" -version = "0.0.2.post2" +version = "0.0.3" description = "An integration package connecting OpenAI and LangChain" authors = [] readme = "README.md" @@ -12,7 +12,7 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" -langchain-core = ">=0.1.7,<0.2" +langchain-core = ">=0.1.13,<0.2" openai = "^1.6.1" numpy = "^1" tiktoken = "^0.5.2" From 4ef0ed4ddc07ed420d98f00faf719533d0517421 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Fri, 19 Jan 2024 13:20:02 -0500 Subject: [PATCH 121/215] astream_events: Add version parameter while method is in beta (#16290) Add a version parameter while the method is in beta phase. The idea is to make it possible to minimize making breaking changes for users while we're iterating on schema. Once the API is stable we can assign a default version requirement. --- libs/core/langchain_core/runnables/base.py | 13 ++++- .../runnables/test_runnable_events.py | 48 ++++++++++++------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index f8b5bcf7c43ff..2d10bf5f834f9 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -699,6 +699,7 @@ async def astream_events( input: Any, config: Optional[RunnableConfig] = None, *, + version: Literal["v1"], include_names: Optional[Sequence[str]] = None, include_types: Optional[Sequence[str]] = None, include_tags: Optional[Sequence[str]] = None, @@ -793,7 +794,9 @@ async def reverse(s: str) -> str: chain = RunnableLambda(func=reverse) - events = [event async for event in chain.astream_events("hello")] + events = [ + event async for event in chain.astream_events("hello", version="v1") + ] # will produce the following events (run_id has been omitted for brevity): [ @@ -823,6 +826,9 @@ async def reverse(s: str) -> str: Args: input: The input to the runnable. config: The config to use for the runnable. + version: The version of the schema to use. + Currently only version 1 is available. + No default will be assigned until the API is stabilized. include_names: Only include events from runnables with matching names. include_types: Only include events from runnables with matching types. include_tags: Only include events from runnables with matching tags. @@ -836,6 +842,11 @@ async def reverse(s: str) -> str: Returns: An async stream of StreamEvents. """ # noqa: E501 + if version != "v1": + raise NotImplementedError( + 'Only version "v1" of the schema is currently supported.' + ) + from langchain_core.runnables.utils import ( _RootEventFilter, ) diff --git a/libs/core/tests/unit_tests/runnables/test_runnable_events.py b/libs/core/tests/unit_tests/runnables/test_runnable_events.py index 2570363b34d72..6d5de1094107d 100644 --- a/libs/core/tests/unit_tests/runnables/test_runnable_events.py +++ b/libs/core/tests/unit_tests/runnables/test_runnable_events.py @@ -53,7 +53,7 @@ def reverse(s: str) -> str: chain = RunnableLambda(func=reverse) - events = await _collect_events(chain.astream_events("hello")) + events = await _collect_events(chain.astream_events("hello", version="v1")) assert events == [ { "data": {"input": "hello"}, @@ -94,7 +94,7 @@ def reverse(s: str) -> str: | r.with_config({"run_name": "2"}) | r.with_config({"run_name": "3"}) ) - events = await _collect_events(chain.astream_events("hello")) + events = await _collect_events(chain.astream_events("hello", version="v1")) assert events == [ { "data": {"input": "hello"}, @@ -209,7 +209,9 @@ def reverse(s: str) -> str: | r.with_config({"run_name": "2", "tags": ["my_tag"]}) | r.with_config({"run_name": "3", "tags": ["my_tag"]}) ) - events = await _collect_events(chain.astream_events("hello", include_names=["1"])) + events = await _collect_events( + chain.astream_events("hello", include_names=["1"], version="v1") + ) assert events == [ { "data": {}, @@ -238,7 +240,9 @@ def reverse(s: str) -> str: ] events = await _collect_events( - chain.astream_events("hello", include_tags=["my_tag"], exclude_names=["2"]) + chain.astream_events( + "hello", include_tags=["my_tag"], exclude_names=["2"], version="v1" + ) ) assert events == [ { @@ -272,7 +276,9 @@ async def test_event_stream_with_lambdas_from_lambda() -> None: as_lambdas = RunnableLambda(lambda x: {"answer": "goodbye"}).with_config( {"run_name": "my_lambda"} ) - events = await _collect_events(as_lambdas.astream_events({"question": "hello"})) + events = await _collect_events( + as_lambdas.astream_events({"question": "hello"}, version="v1") + ) assert events == [ { "data": {"input": {"question": "hello"}}, @@ -331,7 +337,9 @@ async def test_event_stream_with_simple_chain() -> None: } ) - events = await _collect_events(chain.astream_events({"question": "hello"})) + events = await _collect_events( + chain.astream_events({"question": "hello"}, version="v1") + ) assert events == [ { "data": {"input": {"question": "hello"}}, @@ -497,7 +505,7 @@ def with_parameters_and_callbacks(x: int, y: str, callbacks: Callbacks) -> dict: # type ignores below because the tools don't appear to be runnables to type checkers # we can remove as soon as that's fixed - events = await _collect_events(parameterless.astream_events({})) # type: ignore + events = await _collect_events(parameterless.astream_events({}, version="v1")) # type: ignore assert events == [ { "data": {"input": {}}, @@ -525,7 +533,7 @@ def with_parameters_and_callbacks(x: int, y: str, callbacks: Callbacks) -> dict: }, ] - events = await _collect_events(with_callbacks.astream_events({})) # type: ignore + events = await _collect_events(with_callbacks.astream_events({}, version="v1")) # type: ignore assert events == [ { "data": {"input": {}}, @@ -552,7 +560,9 @@ def with_parameters_and_callbacks(x: int, y: str, callbacks: Callbacks) -> dict: "tags": [], }, ] - events = await _collect_events(with_parameters.astream_events({"x": 1, "y": "2"})) # type: ignore + events = await _collect_events( + with_parameters.astream_events({"x": 1, "y": "2"}, version="v1") # type: ignore + ) assert events == [ { "data": {"input": {"x": 1, "y": "2"}}, @@ -581,7 +591,7 @@ def with_parameters_and_callbacks(x: int, y: str, callbacks: Callbacks) -> dict: ] events = await _collect_events( - with_parameters_and_callbacks.astream_events({"x": 1, "y": "2"}) # type: ignore + with_parameters_and_callbacks.astream_events({"x": 1, "y": "2"}, version="v1") # type: ignore ) assert events == [ { @@ -634,7 +644,9 @@ async def test_event_stream_with_retriever() -> None: ), ] ) - events = await _collect_events(retriever.astream_events({"query": "hello"})) + events = await _collect_events( + retriever.astream_events({"query": "hello"}, version="v1") + ) assert events == [ { "data": { @@ -695,7 +707,7 @@ def format_docs(docs: List[Document]) -> str: return ", ".join([doc.page_content for doc in docs]) chain = retriever | format_docs - events = await _collect_events(chain.astream_events("hello")) + events = await _collect_events(chain.astream_events("hello", version="v1")) assert events == [ { "data": {"input": "hello"}, @@ -796,7 +808,9 @@ def reverse(s: str) -> str: # does not appear to be a runnable chain = concat | reverse # type: ignore - events = await _collect_events(chain.astream_events({"a": "hello", "b": "world"})) + events = await _collect_events( + chain.astream_events({"a": "hello", "b": "world"}, version="v1") + ) assert events == [ { "data": {"input": {"a": "hello", "b": "world"}}, @@ -878,7 +892,7 @@ def fail(inputs: str) -> None: chain = RunnableLambda(success) | RunnableLambda(fail).with_retry( stop_after_attempt=1, ) - iterable = chain.astream_events("q") + iterable = chain.astream_events("q", version="v1") events = [] @@ -953,7 +967,9 @@ async def test_with_llm() -> None: llm = FakeStreamingListLLM(responses=["abc"]) chain = prompt | llm - events = await _collect_events(chain.astream_events({"question": "hello"})) + events = await _collect_events( + chain.astream_events({"question": "hello"}, version="v1") + ) assert events == [ { "data": {"input": {"question": "hello"}}, @@ -1061,5 +1077,5 @@ async def add_one(x: int) -> int: assert await add_one_map.ainvoke([1, 2, 3]) == [2, 3, 4] with pytest.raises(NotImplementedError): - async for _ in add_one_map.astream_events([1, 2, 3]): + async for _ in add_one_map.astream_events([1, 2, 3], version="v1"): pass From 1e29b676d5ed1e2fbc1ec1fe2e04a4360dda569f Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:31:54 -0800 Subject: [PATCH 122/215] core[patch]: simple fallback streaming (#16055) --- .../how_to/fallbacks.ipynb | 2 +- .../langchain_core/runnables/fallbacks.py | 118 ++++++++++++++++++ .../unit_tests/runnables/test_fallbacks.py | 61 ++++++++- 3 files changed, 179 insertions(+), 2 deletions(-) diff --git a/docs/docs/expression_language/how_to/fallbacks.ipynb b/docs/docs/expression_language/how_to/fallbacks.ipynb index 23459f8be7376..de915b3240319 100644 --- a/docs/docs/expression_language/how_to/fallbacks.ipynb +++ b/docs/docs/expression_language/how_to/fallbacks.ipynb @@ -302,7 +302,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.9.1" } }, "nbformat": 4, diff --git a/libs/core/langchain_core/runnables/fallbacks.py b/libs/core/langchain_core/runnables/fallbacks.py index 7f8ab1f86637f..bc7128c1bf3f2 100644 --- a/libs/core/langchain_core/runnables/fallbacks.py +++ b/libs/core/langchain_core/runnables/fallbacks.py @@ -2,6 +2,8 @@ from typing import ( TYPE_CHECKING, Any, + AsyncIterator, + Awaitable, Dict, Iterator, List, @@ -30,6 +32,7 @@ Output, get_unique_config_specs, ) +from langchain_core.utils.aiter import py_anext if TYPE_CHECKING: from langchain_core.callbacks.manager import AsyncCallbackManagerForChainRun @@ -415,3 +418,118 @@ async def abatch( raise sorted_handled_exceptions[0][1] to_return.update(handled_exceptions) return [output for _, output in sorted(to_return.items())] # type: ignore + + def stream( + self, + input: Input, + config: Optional[RunnableConfig] = None, + **kwargs: Optional[Any], + ) -> Iterator[Output]: + """""" + if self.exception_key is not None and not isinstance(input, dict): + raise ValueError( + "If 'exception_key' is specified then input must be a dictionary." + f"However found a type of {type(input)} for input" + ) + # setup callbacks + config = ensure_config(config) + callback_manager = get_callback_manager_for_config(config) + # start the root run + run_manager = callback_manager.on_chain_start( + dumpd(self), input, name=config.get("run_name") + ) + first_error = None + last_error = None + for runnable in self.runnables: + try: + if self.exception_key and last_error is not None: + input[self.exception_key] = last_error + stream = runnable.stream( + input, + patch_config(config, callbacks=run_manager.get_child()), + **kwargs, + ) + chunk = next(stream) + except self.exceptions_to_handle as e: + first_error = e if first_error is None else first_error + last_error = e + except BaseException as e: + run_manager.on_chain_error(e) + raise e + else: + first_error = None + break + if first_error: + run_manager.on_chain_error(first_error) + raise first_error + + yield chunk + output: Optional[Output] = chunk + try: + for chunk in stream: + yield chunk + try: + output = output + chunk # type: ignore + except TypeError: + output = None + except BaseException as e: + run_manager.on_chain_error(e) + raise e + run_manager.on_chain_end(output) + + async def astream( + self, + input: Input, + config: Optional[RunnableConfig] = None, + **kwargs: Optional[Any], + ) -> AsyncIterator[Output]: + if self.exception_key is not None and not isinstance(input, dict): + raise ValueError( + "If 'exception_key' is specified then input must be a dictionary." + f"However found a type of {type(input)} for input" + ) + # setup callbacks + config = ensure_config(config) + callback_manager = get_async_callback_manager_for_config(config) + # start the root run + run_manager = await callback_manager.on_chain_start( + dumpd(self), input, name=config.get("run_name") + ) + first_error = None + last_error = None + for runnable in self.runnables: + try: + if self.exception_key and last_error is not None: + input[self.exception_key] = last_error + stream = runnable.astream( + input, + patch_config(config, callbacks=run_manager.get_child()), + **kwargs, + ) + chunk = await cast(Awaitable[Output], py_anext(stream)) + except self.exceptions_to_handle as e: + first_error = e if first_error is None else first_error + last_error = e + except BaseException as e: + await run_manager.on_chain_error(e) + raise e + else: + first_error = None + break + if first_error: + await run_manager.on_chain_error(first_error) + raise first_error + + yield chunk + output: Optional[Output] = chunk + try: + async for chunk in stream: + yield chunk + try: + output = output + chunk # type: ignore + except TypeError: + output = None + except BaseException as e: + await run_manager.on_chain_error(e) + raise e + await run_manager.on_chain_end(output) diff --git a/libs/core/tests/unit_tests/runnables/test_fallbacks.py b/libs/core/tests/unit_tests/runnables/test_fallbacks.py index ecd9cb6fc9f83..de1447a7267a1 100644 --- a/libs/core/tests/unit_tests/runnables/test_fallbacks.py +++ b/libs/core/tests/unit_tests/runnables/test_fallbacks.py @@ -1,5 +1,5 @@ import sys -from typing import Any +from typing import Any, AsyncIterator, Iterator import pytest from syrupy import SnapshotAssertion @@ -8,6 +8,7 @@ from langchain_core.prompts import PromptTemplate from langchain_core.runnables import ( Runnable, + RunnableGenerator, RunnableLambda, RunnableParallel, RunnablePassthrough, @@ -229,3 +230,61 @@ async def test_abatch() -> None: expected = ["first", "second", RuntimeError()] _assert_potential_error(actual, expected) + + +def _generate(input: Iterator) -> Iterator[str]: + yield from "foo bar" + + +def _generate_immediate_error(input: Iterator) -> Iterator[str]: + raise ValueError() + yield "" + + +def _generate_delayed_error(input: Iterator) -> Iterator[str]: + yield "" + raise ValueError() + + +def test_fallbacks_stream() -> None: + runnable = RunnableGenerator(_generate_immediate_error).with_fallbacks( + [RunnableGenerator(_generate)] + ) + assert list(runnable.stream({})) == [c for c in "foo bar"] + + with pytest.raises(ValueError): + runnable = RunnableGenerator(_generate_delayed_error).with_fallbacks( + [RunnableGenerator(_generate)] + ) + list(runnable.stream({})) + + +async def _agenerate(input: AsyncIterator) -> AsyncIterator[str]: + for c in "foo bar": + yield c + + +async def _agenerate_immediate_error(input: AsyncIterator) -> AsyncIterator[str]: + raise ValueError() + yield "" + + +async def _agenerate_delayed_error(input: AsyncIterator) -> AsyncIterator[str]: + yield "" + raise ValueError() + + +async def test_fallbacks_astream() -> None: + runnable = RunnableGenerator(_agenerate_immediate_error).with_fallbacks( + [RunnableGenerator(_agenerate)] + ) + expected = (c for c in "foo bar") + async for c in runnable.astream({}): + assert c == next(expected) + + with pytest.raises(ValueError): + runnable = RunnableGenerator(_agenerate_delayed_error).with_fallbacks( + [RunnableGenerator(_agenerate)] + ) + async for c in runnable.astream({}): + pass From ffae98d3711a164cd2e9211546a1c04485b1f50b Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch <ofermend@gmail.com> Date: Fri, 19 Jan 2024 17:32:33 -0800 Subject: [PATCH 123/215] template: Update Vectara templates (#15363) fixed multi-query template for Vectara added self-query template for Vectara Also added prompt_name parameter to summarization CC @efriis **Twitter handle:** @ofermend --- .../langchain_community/vectorstores/vectara.py | 5 +++++ templates/rag-vectara-multiquery/README.md | 10 +++++----- .../rag_vectara_multiquery/chain.py | 1 - 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/vectara.py b/libs/community/langchain_community/vectorstores/vectara.py index 4a9334b3fbb4b..0fc910042168f 100644 --- a/libs/community/langchain_community/vectorstores/vectara.py +++ b/libs/community/langchain_community/vectorstores/vectara.py @@ -22,11 +22,14 @@ class SummaryConfig: is_enabled: True if summary is enabled, False otherwise max_results: maximum number of results to summarize response_lang: requested language for the summary + prompt_name: name of the prompt to use for summarization + (see https://docs.vectara.com/docs/learn/grounded-generation/select-a-summarizer) """ is_enabled: bool = False max_results: int = 7 response_lang: str = "eng" + prompt_name: str = "vectara-summary-ext-v1.2.0" @dataclass @@ -364,6 +367,7 @@ def vectara_query( { "maxSummarizedResults": config.summary_config.max_results, "responseLang": config.summary_config.response_lang, + "summarizerPromptName": config.summary_config.prompt_name, } ] @@ -570,6 +574,7 @@ class VectaraRetriever(VectorStoreRetriever): "k": 5, "filter": "", "n_sentence_context": "2", + "summary_config": SummaryConfig(), } ) diff --git a/templates/rag-vectara-multiquery/README.md b/templates/rag-vectara-multiquery/README.md index b12b67942d413..e2018a51937ea 100644 --- a/templates/rag-vectara-multiquery/README.md +++ b/templates/rag-vectara-multiquery/README.md @@ -23,20 +23,20 @@ pip install -U langchain-cli To create a new LangChain project and install this as the only package, you can do: ```shell -langchain app new my-app --package rag-vectara +langchain app new my-app --package rag-vectara-multiquery ``` If you want to add this to an existing project, you can just run: ```shell -langchain app add rag-vectara +langchain app add rag-vectara-multiquery ``` And add the following code to your `server.py` file: ```python from rag_vectara import chain as rag_vectara_chain -add_routes(app, rag_vectara_chain, path="/rag-vectara") +add_routes(app, rag_vectara_chain, path="/rag-vectara-multiquery") ``` (Optional) Let's now configure LangSmith. @@ -61,12 +61,12 @@ This will start the FastAPI app with a server is running locally at [http://localhost:8000](http://localhost:8000) We can see all templates at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) -We can access the playground at [http://127.0.0.1:8000/rag-vectara/playground](http://127.0.0.1:8000/rag-vectara/playground) +We can access the playground at [http://127.0.0.1:8000/rag-vectara-multiquery/playground](http://127.0.0.1:8000/rag-vectara-multiquery/playground) We can access the template from code with: ```python from langserve.client import RemoteRunnable -runnable = RemoteRunnable("http://localhost:8000/rag-vectara") +runnable = RemoteRunnable("http://localhost:8000/rag-vectara-multiquery") ``` diff --git a/templates/rag-vectara-multiquery/rag_vectara_multiquery/chain.py b/templates/rag-vectara-multiquery/rag_vectara_multiquery/chain.py index 9b769e9bd04b5..1fd9354b7fa37 100644 --- a/templates/rag-vectara-multiquery/rag_vectara_multiquery/chain.py +++ b/templates/rag-vectara-multiquery/rag_vectara_multiquery/chain.py @@ -41,7 +41,6 @@ # We extract the summary from the RAG output, which is the last document # (if summary is enabled) # Note that if you want to extract the citation information, you can use res[:-1]] -model = ChatOpenAI() chain = ( RunnableParallel({"context": retriever, "question": RunnablePassthrough()}) | (lambda res: res[-1]) From 3d23a5eb36045db3b7a05c34947b74bd4909ba3b Mon Sep 17 00:00:00 2001 From: Ryan French <ryan@ryanfrench.co> Date: Sat, 20 Jan 2024 01:57:18 +0000 Subject: [PATCH 124/215] langchain[patch]: Allow OpenSearch Query Translator to correctly work with Date types (#16022) **Description:** Fixes an issue where the Date type in an OpenSearch Self Querying Retriever would fail to generate a valid query **Issue:** https://github.com/langchain-ai/langchain/issues/14225 --- .../retrievers/self_query/opensearch.py | 28 +++++- .../retrievers/self_query/test_opensearch.py | 87 +++++++++++++++++++ 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/libs/langchain/langchain/retrievers/self_query/opensearch.py b/libs/langchain/langchain/retrievers/self_query/opensearch.py index 502ee8a872b1f..bb27cddd0b481 100644 --- a/libs/langchain/langchain/retrievers/self_query/opensearch.py +++ b/libs/langchain/langchain/retrievers/self_query/opensearch.py @@ -58,11 +58,25 @@ def visit_comparison(self, comparison: Comparison) -> Dict: Comparator.GT, Comparator.GTE, ]: - return { - "range": { - field: {self._format_func(comparison.comparator): comparison.value} + if isinstance(comparison.value, dict): + if "date" in comparison.value: + return { + "range": { + field: { + self._format_func( + comparison.comparator + ): comparison.value["date"] + } + } + } + else: + return { + "range": { + field: { + self._format_func(comparison.comparator): comparison.value + } + } } - } if comparison.comparator == Comparator.LIKE: return { @@ -70,8 +84,13 @@ def visit_comparison(self, comparison: Comparison) -> Dict: field: {"value": comparison.value} } } + field = f"{field}.keyword" if isinstance(comparison.value, str) else field + if isinstance(comparison.value, dict): + if "date" in comparison.value: + comparison.value = comparison.value["date"] + return {self._format_func(comparison.comparator): {field: comparison.value}} def visit_structured_query( @@ -81,4 +100,5 @@ def visit_structured_query( kwargs = {} else: kwargs = {"filter": structured_query.filter.accept(self)} + return structured_query.query, kwargs diff --git a/libs/langchain/tests/unit_tests/retrievers/self_query/test_opensearch.py b/libs/langchain/tests/unit_tests/retrievers/self_query/test_opensearch.py index 629d195402e31..93b6629ecb01e 100644 --- a/libs/langchain/tests/unit_tests/retrievers/self_query/test_opensearch.py +++ b/libs/langchain/tests/unit_tests/retrievers/self_query/test_opensearch.py @@ -85,3 +85,90 @@ def test_visit_structured_query() -> None: ) actual = DEFAULT_TRANSLATOR.visit_structured_query(structured_query) assert expected == actual + + +def test_visit_structured_query_with_date_range() -> None: + query = "Who was the president of France in 1995?" + operation = Operation( + operator=Operator.AND, + arguments=[ + Comparison(comparator=Comparator.EQ, attribute="foo", value="20"), + Operation( + operator=Operator.AND, + arguments=[ + Comparison( + comparator=Comparator.GTE, + attribute="timestamp", + value={"date": "1995-01-01", "type": "date"}, + ), + Comparison( + comparator=Comparator.LT, + attribute="timestamp", + value={"date": "1996-01-01", "type": "date"}, + ), + ], + ), + ], + ) + structured_query = StructuredQuery(query=query, filter=operation, limit=None) + expected = ( + query, + { + "filter": { + "bool": { + "must": [ + {"term": {"metadata.foo.keyword": "20"}}, + { + "bool": { + "must": [ + { + "range": { + "metadata.timestamp": {"gte": "1995-01-01"} + } + }, + { + "range": { + "metadata.timestamp": {"lt": "1996-01-01"} + } + }, + ] + } + }, + ] + } + } + }, + ) + actual = DEFAULT_TRANSLATOR.visit_structured_query(structured_query) + assert expected == actual + + +def test_visit_structured_query_with_date() -> None: + query = "Who was the president of France on 1st of January 1995?" + operation = Operation( + operator=Operator.AND, + arguments=[ + Comparison(comparator=Comparator.EQ, attribute="foo", value="20"), + Comparison( + comparator=Comparator.EQ, + attribute="timestamp", + value={"date": "1995-01-01", "type": "date"}, + ), + ], + ) + structured_query = StructuredQuery(query=query, filter=operation, limit=None) + expected = ( + query, + { + "filter": { + "bool": { + "must": [ + {"term": {"metadata.foo.keyword": "20"}}, + {"term": {"metadata.timestamp": "1995-01-01"}}, + ] + } + } + }, + ) + actual = DEFAULT_TRANSLATOR.visit_structured_query(structured_query) + assert expected == actual From ef75bb63ce5cc4fb76ba1631ebe582f56103ab7e Mon Sep 17 00:00:00 2001 From: Nuno Campos <nuno@langchain.dev> Date: Sat, 20 Jan 2024 18:52:26 -0800 Subject: [PATCH 125/215] core[patch] Fix tracer output of streamed runs with non-addable output (#16324) - Used to be None, now is just the last chunk <!-- Thank you for contributing to LangChain! Please title your PR "<package>: <description>", where <package> is whichever of langchain, community, core, experimental, etc. is being modified. Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes if applicable, - **Dependencies:** any dependencies required for this change, - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` from the root of the package you've modified to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ 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. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> --- libs/core/langchain_core/runnables/base.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index 2d10bf5f834f9..cdaab4015b5d1 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -1503,8 +1503,10 @@ def _transform_stream_with_config( try: final_output = final_output + chunk # type: ignore except TypeError: - final_output = None + final_output = chunk final_output_supported = False + else: + final_output = chunk except StopIteration: pass for ichunk in input_for_tracing: @@ -1515,8 +1517,10 @@ def _transform_stream_with_config( try: final_input = final_input + ichunk # type: ignore except TypeError: - final_input = None + final_input = ichunk final_input_supported = False + else: + final_input = ichunk except BaseException as e: run_manager.on_chain_error(e, inputs=final_input) raise @@ -1602,8 +1606,10 @@ async def _atransform_stream_with_config( try: final_output = final_output + chunk # type: ignore except TypeError: - final_output = None + final_output = chunk final_output_supported = False + else: + final_output = chunk except StopAsyncIteration: pass async for ichunk in input_for_tracing: @@ -1614,8 +1620,10 @@ async def _atransform_stream_with_config( try: final_input = final_input + ichunk # type: ignore[operator] except TypeError: - final_input = None + final_input = ichunk final_input_supported = False + else: + final_input = ichunk except BaseException as e: await run_manager.on_chain_error(e, inputs=final_input) raise From c2a614eddce272afedc1af798e2ca68589a6fc3b Mon Sep 17 00:00:00 2001 From: Virat Singh <virat.dot@gmail.com> Date: Sun, 21 Jan 2024 18:08:55 -0500 Subject: [PATCH 126/215] community: Add PolygonLastQuote Tool and Toolkit (#15990) **Description:** In this PR, I am adding a `PolygonLastQuote` Tool, which can be used to get the latest price quote for a given ticker / stock. Additionally, I've added a Polygon Toolkit, which we can use to encapsulate future tools that we build for Polygon. **Twitter handle:** [@virattt](https://twitter.com/virattt) --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- docs/docs/integrations/toolkits/gmail.ipynb | 2 +- docs/docs/integrations/toolkits/polygon.ipynb | 187 ++++++++++++++++++ docs/docs/integrations/tools/polygon.ipynb | 29 +-- .../agent_toolkits/__init__.py | 2 + .../agent_toolkits/polygon/__init__.py | 1 + .../agent_toolkits/polygon/toolkit.py | 27 +++ .../langchain_community/tools/__init__.py | 9 + .../tools/polygon/__init__.py | 7 + .../tools/polygon/last_quote.py | 34 ++++ .../unit_tests/agent_toolkits/test_imports.py | 1 + .../tests/unit_tests/tools/test_imports.py | 1 + .../tests/unit_tests/tools/test_public_api.py | 1 + 12 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 docs/docs/integrations/toolkits/polygon.ipynb create mode 100644 libs/community/langchain_community/agent_toolkits/polygon/__init__.py create mode 100644 libs/community/langchain_community/agent_toolkits/polygon/toolkit.py create mode 100644 libs/community/langchain_community/tools/polygon/__init__.py create mode 100644 libs/community/langchain_community/tools/polygon/last_quote.py diff --git a/docs/docs/integrations/toolkits/gmail.ipynb b/docs/docs/integrations/toolkits/gmail.ipynb index dcbcadabc22ab..d9cafb0f5f5c6 100644 --- a/docs/docs/integrations/toolkits/gmail.ipynb +++ b/docs/docs/integrations/toolkits/gmail.ipynb @@ -294,7 +294,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.10.1" } }, "nbformat": 4, diff --git a/docs/docs/integrations/toolkits/polygon.ipynb b/docs/docs/integrations/toolkits/polygon.ipynb new file mode 100644 index 0000000000000..87c8aa0af9982 --- /dev/null +++ b/docs/docs/integrations/toolkits/polygon.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e6fd05db-21c2-4227-9900-0840bc62cb31", + "metadata": {}, + "source": [ + "# Polygon IO Toolkit\n", + "\n", + "This notebook shows how to use agents to interact with the [Polygon IO](https://polygon.io/) toolkit. The toolkit provides access to Polygon's Stock Market Data API." + ] + }, + { + "cell_type": "markdown", + "id": "a4da342d", + "metadata": {}, + "source": [ + "## Example Use\n", + "\n", + "\n", + "### Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c17b33e0", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-community > /dev/null" + ] + }, + { + "cell_type": "markdown", + "id": "3cd00ad2", + "metadata": {}, + "source": [ + "Get your Polygon IO API key [here](https://polygon.io/), and then set it below.\n", + "Note that the tool used in this example requires a \"Stocks Advanced\" subscription" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a180a2b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "········\n" + ] + } + ], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"POLYGON_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "ed6f89fa", + "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": "56670cf6", + "metadata": {}, + "outputs": [], + "source": [ + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "7d93e6bd-03d7-4d3c-b915-8b73164e2ad8", + "metadata": {}, + "source": [ + "### Initializing the agent" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "648a2cb2-308e-4b2e-9b73-37109be4e258", + "metadata": { + "is_executing": true + }, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain_community.agent_toolkits.polygon.toolkit import PolygonToolkit\n", + "from langchain_community.utilities.polygon import PolygonAPIWrapper\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(temperature=0)\n", + "\n", + "instructions = \"\"\"You are an assistant.\"\"\"\n", + "base_prompt = hub.pull(\"langchain-ai/openai-functions-template\")\n", + "prompt = base_prompt.partial(instructions=instructions)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "18650040-0ff8-4c0f-a4f2-be6aad7fe63e", + "metadata": {}, + "outputs": [], + "source": [ + "polygon = PolygonAPIWrapper()\n", + "toolkit = PolygonToolkit.from_polygon_api_wrapper(polygon)\n", + "agent = create_openai_functions_agent(llm, toolkit.get_tools(), prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fd7463e4-8716-4d1d-860a-770533eaa742", + "metadata": {}, + "outputs": [], + "source": [ + "agent_executor = AgentExecutor(\n", + " agent=agent,\n", + " tools=toolkit.get_tools(),\n", + " verbose=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "71f05fc9-d80d-4614-b9a3-e0a5e43cbbbb", + "metadata": {}, + "source": [ + "### Get the last price quote for a stock" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b97409f3-dc87-425d-b555-406cf8466a28", + "metadata": {}, + "outputs": [], + "source": [ + "agent_executor.invoke({\"input\": \"What is the latest stock price for AAPL?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e666ee1", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/integrations/tools/polygon.ipynb b/docs/docs/integrations/tools/polygon.ipynb index 7f7be71168984..62b078d0d8efa 100644 --- a/docs/docs/integrations/tools/polygon.ipynb +++ b/docs/docs/integrations/tools/polygon.ipynb @@ -40,6 +40,7 @@ }, "outputs": [], "source": [ + "from langchain_community.tools.polygon.last_quote import PolygonLastQuote\n", "from langchain_community.utilities.polygon import PolygonAPIWrapper" ] }, @@ -52,7 +53,7 @@ }, "outputs": [], "source": [ - "polygon = PolygonAPIWrapper()" + "tool = PolygonLastQuote(api_wrapper=PolygonAPIWrapper())" ] }, { @@ -63,20 +64,20 @@ "id": "068991a6", "outputId": "c5cdc6ec-03cf-4084-cc6f-6ae792d91d39" }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'results': {'P': 185.86, 'S': 1, 'T': 'AAPL', 'X': 11, 'i': [604], 'p': 185.81, 'q': 106551669, 's': 2, 't': 1705098436014023700, 'x': 12, 'y': 1705098436014009300, 'z': 3}}" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [ + { + "data": { + "text/plain": [ + "{'results': {'P': 185.86, 'S': 1, 'T': 'AAPL', 'X': 11, 'i': [604], 'p': 185.81, 'q': 106551669, 's': 2, 't': 1705098436014023700, 'x': 12, 'y': 1705098436014009300, 'z': 3}}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "polygon.run(\"get_last_quote\", \"AAPL\")" + "tool.run(\"AAPL\")" ] } ], diff --git a/libs/community/langchain_community/agent_toolkits/__init__.py b/libs/community/langchain_community/agent_toolkits/__init__.py index b39e2751f7e9d..9b5279ee85fe5 100644 --- a/libs/community/langchain_community/agent_toolkits/__init__.py +++ b/libs/community/langchain_community/agent_toolkits/__init__.py @@ -34,6 +34,7 @@ from langchain_community.agent_toolkits.playwright.toolkit import ( PlayWrightBrowserToolkit, ) +from langchain_community.agent_toolkits.polygon.toolkit import PolygonToolkit from langchain_community.agent_toolkits.powerbi.base import create_pbi_agent from langchain_community.agent_toolkits.powerbi.chat_base import create_pbi_chat_agent from langchain_community.agent_toolkits.powerbi.toolkit import PowerBIToolkit @@ -59,6 +60,7 @@ "O365Toolkit", "OpenAPIToolkit", "PlayWrightBrowserToolkit", + "PolygonToolkit", "PowerBIToolkit", "SlackToolkit", "SteamToolkit", diff --git a/libs/community/langchain_community/agent_toolkits/polygon/__init__.py b/libs/community/langchain_community/agent_toolkits/polygon/__init__.py new file mode 100644 index 0000000000000..b9a3bf13b76c5 --- /dev/null +++ b/libs/community/langchain_community/agent_toolkits/polygon/__init__.py @@ -0,0 +1 @@ +"""Polygon Toolkit""" diff --git a/libs/community/langchain_community/agent_toolkits/polygon/toolkit.py b/libs/community/langchain_community/agent_toolkits/polygon/toolkit.py new file mode 100644 index 0000000000000..748c84c1eef9d --- /dev/null +++ b/libs/community/langchain_community/agent_toolkits/polygon/toolkit.py @@ -0,0 +1,27 @@ +from typing import List + +from langchain_community.agent_toolkits.base import BaseToolkit +from langchain_community.tools import BaseTool +from langchain_community.tools.polygon import PolygonLastQuote +from langchain_community.utilities.polygon import PolygonAPIWrapper + + +class PolygonToolkit(BaseToolkit): + """Polygon Toolkit.""" + + tools: List[BaseTool] = [] + + @classmethod + def from_polygon_api_wrapper( + cls, polygon_api_wrapper: PolygonAPIWrapper + ) -> "PolygonToolkit": + tools = [ + PolygonLastQuote( + api_wrapper=polygon_api_wrapper, + ) + ] + return cls(tools=tools) + + def get_tools(self) -> List[BaseTool]: + """Get the tools in the toolkit.""" + return self.tools diff --git a/libs/community/langchain_community/tools/__init__.py b/libs/community/langchain_community/tools/__init__.py index 8bce838f88129..b00e02387103d 100644 --- a/libs/community/langchain_community/tools/__init__.py +++ b/libs/community/langchain_community/tools/__init__.py @@ -472,6 +472,12 @@ def _import_plugin() -> Any: return AIPluginTool +def _import_polygon_tool_PolygonLastQuote() -> Any: + from langchain_community.tools.polygon.last_quote import PolygonLastQuote + + return PolygonLastQuote + + def _import_powerbi_tool_InfoPowerBITool() -> Any: from langchain_community.tools.powerbi.tool import InfoPowerBITool @@ -907,6 +913,8 @@ def __getattr__(name: str) -> Any: return _import_playwright_NavigateTool() elif name == "AIPluginTool": return _import_plugin() + elif name == "PolygonLastQuote": + return _import_polygon_tool_PolygonLastQuote() elif name == "InfoPowerBITool": return _import_powerbi_tool_InfoPowerBITool() elif name == "ListPowerBITool": @@ -1085,6 +1093,7 @@ def __getattr__(name: str) -> Any: "OpenAPISpec", "OpenWeatherMapQueryRun", "PubmedQueryRun", + "PolygonLastQuote", "RedditSearchRun", "QueryCheckerTool", "QueryPowerBITool", diff --git a/libs/community/langchain_community/tools/polygon/__init__.py b/libs/community/langchain_community/tools/polygon/__init__.py new file mode 100644 index 0000000000000..acc8bc4ac70c7 --- /dev/null +++ b/libs/community/langchain_community/tools/polygon/__init__.py @@ -0,0 +1,7 @@ +"""Polygon IO tools.""" + +from langchain_community.tools.polygon.last_quote import PolygonLastQuote + +__all__ = [ + "PolygonLastQuote", +] diff --git a/libs/community/langchain_community/tools/polygon/last_quote.py b/libs/community/langchain_community/tools/polygon/last_quote.py new file mode 100644 index 0000000000000..55fe3d9301020 --- /dev/null +++ b/libs/community/langchain_community/tools/polygon/last_quote.py @@ -0,0 +1,34 @@ +from typing import Optional, Type + +from langchain_core.callbacks import CallbackManagerForToolRun +from langchain_core.pydantic_v1 import BaseModel +from langchain_core.tools import BaseTool + +from langchain_community.utilities.polygon import PolygonAPIWrapper + + +class Inputs(BaseModel): + query: str + + +class PolygonLastQuote(BaseTool): + """Tool that gets the last quote of a ticker from Polygon""" + + mode: str = "get_last_quote" + name: str = "polygon_last_quote" + description: str = ( + "A wrapper around Polygon's Last Quote API. " + "This tool is useful for fetching the latest price of a stock. " + "Input should be the ticker that you want to query the last price quote for." + ) + args_schema: Type[BaseModel] = Inputs + + api_wrapper: PolygonAPIWrapper + + def _run( + self, + query: str, + run_manager: Optional[CallbackManagerForToolRun] = None, + ) -> str: + """Use the Polygon API tool.""" + return self.api_wrapper.run(self.mode, ticker=query) diff --git a/libs/community/tests/unit_tests/agent_toolkits/test_imports.py b/libs/community/tests/unit_tests/agent_toolkits/test_imports.py index c6557b7391ee8..416b66849b38f 100644 --- a/libs/community/tests/unit_tests/agent_toolkits/test_imports.py +++ b/libs/community/tests/unit_tests/agent_toolkits/test_imports.py @@ -14,6 +14,7 @@ "O365Toolkit", "OpenAPIToolkit", "PlayWrightBrowserToolkit", + "PolygonToolkit", "PowerBIToolkit", "SlackToolkit", "SteamToolkit", diff --git a/libs/community/tests/unit_tests/tools/test_imports.py b/libs/community/tests/unit_tests/tools/test_imports.py index 424540de098dd..9fdcf157e9638 100644 --- a/libs/community/tests/unit_tests/tools/test_imports.py +++ b/libs/community/tests/unit_tests/tools/test_imports.py @@ -79,6 +79,7 @@ "OpenAPISpec", "OpenWeatherMapQueryRun", "PubmedQueryRun", + "PolygonLastQuote", "RedditSearchRun", "QueryCheckerTool", "QueryPowerBITool", diff --git a/libs/community/tests/unit_tests/tools/test_public_api.py b/libs/community/tests/unit_tests/tools/test_public_api.py index 624262f3d1967..0f6102c45e4be 100644 --- a/libs/community/tests/unit_tests/tools/test_public_api.py +++ b/libs/community/tests/unit_tests/tools/test_public_api.py @@ -81,6 +81,7 @@ "OpenAPISpec", "OpenWeatherMapQueryRun", "PubmedQueryRun", + "PolygonLastQuote", "RedditSearchRun", "QueryCheckerTool", "QueryPowerBITool", From 5396604ef4822abdc812baadec456af072d5f592 Mon Sep 17 00:00:00 2001 From: Luke <lpezet@gmail.com> Date: Sun, 21 Jan 2024 19:11:45 -0700 Subject: [PATCH 127/215] community: Handling missing key in Google Trends API response. (#15864) - **Description:** Handing response where _interest_over_time_ is missing. - **Issue:** #15859 - **Dependencies:** None --- .../utilities/google_trends.py | 7 +++- .../utilities/test_google_trends.py | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 libs/community/tests/integration_tests/utilities/test_google_trends.py diff --git a/libs/community/langchain_community/utilities/google_trends.py b/libs/community/langchain_community/utilities/google_trends.py index f0f15000c8a1c..59df34485606f 100644 --- a/libs/community/langchain_community/utilities/google_trends.py +++ b/libs/community/langchain_community/utilities/google_trends.py @@ -65,7 +65,12 @@ def run(self, query: str) -> str: total_results = [] client = self.serp_search_engine(params) - total_results = client.get_dict()["interest_over_time"]["timeline_data"] + client_dict = client.get_dict() + total_results = ( + client_dict["interest_over_time"]["timeline_data"] + if "interest_over_time" in client_dict + else None + ) if not total_results: return "No good Trend Result was found" diff --git a/libs/community/tests/integration_tests/utilities/test_google_trends.py b/libs/community/tests/integration_tests/utilities/test_google_trends.py new file mode 100644 index 0000000000000..0455f16a58060 --- /dev/null +++ b/libs/community/tests/integration_tests/utilities/test_google_trends.py @@ -0,0 +1,33 @@ +"""Unit test for Google Trends API Wrapper.""" +import os +from unittest.mock import patch + +from langchain_community.utilities.google_trends import GoogleTrendsAPIWrapper + + +@patch("serpapi.SerpApiClient.get_json") +def test_unexpected_response(mocked_serpapiclient): + os.environ["SERPAPI_API_KEY"] = "123abcd" + resp = { + "search_metadata": { + "id": "659f32ec36e6a9107b46b5b4", + "status": "Error", + "json_endpoint": "https://serpapi.com/searches/.../659f32ec36e6a9107b46b5b4.json", + "created_at": "2024-01-11 00:14:36 UTC", + "processed_at": "2024-01-11 00:14:36 UTC", + "google_trends_url": "https://trends.google.com/trends/api/explore?tz=420&req=%7B%22comparisonItem%22%3A%5B%7B%22keyword%22%3A%22Lego+building+trends+2022%22%2C%22geo%22%3A%22%22%2C%22time%22%3A%22today+12-m%22%7D%5D%2C%22category%22%3A0%2C%22property%22%3A%22%22%2C%22userConfig%22%3A%22%7BuserType%3A+%5C%22USER_TYPE_LEGIT_USER%5C%22%7D%22%7D", + "prettify_html_file": "https://serpapi.com/searches/.../659f32ec36e6a9107b46b5b4.prettify", + "total_time_taken": 90.14, + }, + "search_parameters": { + "engine": "google_trends", + "q": "Lego building trends 2022", + "date": "today 12-m", + "tz": "420", + "data_type": "TIMESERIES", + }, + "error": "We couldn't get valid ... Please try again later.", + } + mocked_serpapiclient.return_value = resp + tool = GoogleTrendsAPIWrapper() + tool.run("does not matter") From 89372fca22ca47ede37f6ecae6c83d277804bfe5 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Mon, 22 Jan 2024 10:18:04 -0500 Subject: [PATCH 128/215] core[patch]: Update sys info information (#16297) Update information collected in sys info. python -m langchain_core.sys_info System Information ------------------ > OS: Linux > OS Version: #14~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Nov 20 18:15:30 UTC 2 > Python Version: 3.11.4 (main, Sep 25 2023, 10:06:23) [GCC 11.4.0] Package Information ------------------- > langchain_core: 0.1.10 > langchain: 0.1.0 > langchain_community: 0.0.11 > langchain_cli: 0.0.20 > langchain_experimental: 0.0.36 > langchain_openai: 0.0.2 > langchainhub: 0.1.14 > langserve: 0.0.19 Packages not installed (Not Necessarily a Problem) -------------------------------------------------- The following packages were not found: > langgraph --- libs/core/langchain_core/sys_info.py | 43 ++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/libs/core/langchain_core/sys_info.py b/libs/core/langchain_core/sys_info.py index c4ffd798614c9..467d125616fa6 100644 --- a/libs/core/langchain_core/sys_info.py +++ b/libs/core/langchain_core/sys_info.py @@ -4,16 +4,32 @@ def print_sys_info(*, additional_pkgs: Sequence[str] = tuple()) -> None: """Print information about the environment for debugging purposes.""" + import pkgutil import platform import sys from importlib import metadata, util - packages = [ - "langchain_core", - "langchain", - "langchain_community", + # Packages that do not start with "langchain" prefix. + other_langchain_packages = [ "langserve", - ] + list(additional_pkgs) + "langgraph", + ] + + langchain_pkgs = [ + name for _, name, _ in pkgutil.iter_modules() if name.startswith("langchain") + ] + + all_packages = sorted( + set(langchain_pkgs + other_langchain_packages + list(additional_pkgs)) + ) + + # Always surface these packages to the top + order_by = ["langchain_core", "langchain", "langchain_community"] + + for pkg in reversed(order_by): + if pkg in all_packages: + all_packages.remove(pkg) + all_packages = [pkg] + list(all_packages) system_info = { "OS": platform.system(), @@ -32,13 +48,15 @@ def print_sys_info(*, additional_pkgs: Sequence[str] = tuple()) -> None: print("Package Information") print("-------------------") - for pkg in packages: + not_installed = [] + + for pkg in all_packages: try: found_package = util.find_spec(pkg) except Exception: found_package = None if found_package is None: - print(f"> {pkg}: Not Found") + not_installed.append(pkg) continue # Package version @@ -51,7 +69,16 @@ def print_sys_info(*, additional_pkgs: Sequence[str] = tuple()) -> None: if package_version is not None: print(f"> {pkg}: {package_version}") else: - print(f"> {pkg}: Found") + print(f"> {pkg}: Installed. No version info available.") + + if not_installed: + print() + print("Packages not installed (Not Necessarily a Problem)") + print("--------------------------------------------------") + print("The following packages were not found:") + print() + for pkg in not_installed: + print(f"> {pkg}") if __name__ == "__main__": From c6bd7778b003fe5b6d087a38d4bdcfe60ca6feae Mon Sep 17 00:00:00 2001 From: Bob Lin <bob401710@gmail.com> Date: Mon, 22 Jan 2024 09:18:43 -0600 Subject: [PATCH 129/215] Use `invoke` instead of `__call__` (#16369) The following warning information will be displayed when i use `llm(PROMPT)`: ```python /Users/169/llama.cpp/venv/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `__call__` was deprecated in LangChain 0.1.7 and will be removed in 0.2.0. Use invoke instead. warn_deprecated( ``` So I changed to standard usage. --- docs/docs/integrations/llms/llamacpp.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/integrations/llms/llamacpp.ipynb b/docs/docs/integrations/llms/llamacpp.ipynb index c433baf1d027c..853787fc198a6 100644 --- a/docs/docs/integrations/llms/llamacpp.ipynb +++ b/docs/docs/integrations/llms/llamacpp.ipynb @@ -316,7 +316,7 @@ "prompt = \"\"\"\n", "Question: A rap battle between Stephen Colbert and John Oliver\n", "\"\"\"\n", - "llm(prompt)" + "llm.invoke(prompt)" ] }, { @@ -618,7 +618,7 @@ ], "source": [ "%%capture captured --no-stdout\n", - "result = llm(\"Describe a person in JSON format:\")" + "result = llm.invoke(\"Describe a person in JSON format:\")" ] }, { @@ -674,7 +674,7 @@ ], "source": [ "%%capture captured --no-stdout\n", - "result = llm(\"List of top-3 my favourite books:\")" + "result = llm.invoke(\"List of top-3 my favourite books:\")" ] } ], From 076dbb1a8f62e579859e67e96c14ac045d094cd2 Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk <139469471+MateuszOssGit@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:22:03 +0100 Subject: [PATCH 130/215] docs: IBM watsonx.ai Use `invoke` instead of `__call__` (#16371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Description:** Updating documentation of IBM [watsonx.ai](https://www.ibm.com/products/watsonx-ai) LLM with using `invoke` instead of `__call__` - **Dependencies:** [ibm-watsonx-ai](https://pypi.org/project/ibm-watsonx-ai/), - **Tag maintainer:** : Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. ✅ The following warning information show when i use `run` and `__call__` method: ``` LangChainDeprecationWarning: The function `__call__` was deprecated in LangChain 0.1.7 and will be removed in 0.2.0. Use invoke instead. warn_deprecated( ``` We need to update documentation for using `invoke` method --- docs/docs/integrations/llms/watsonxllm.ipynb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/docs/integrations/llms/watsonxllm.ipynb b/docs/docs/integrations/llms/watsonxllm.ipynb index be5d0841cd2e2..f0b142cf96d46 100644 --- a/docs/docs/integrations/llms/watsonxllm.ipynb +++ b/docs/docs/integrations/llms/watsonxllm.ipynb @@ -176,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "c7d80c05", "metadata": {}, "outputs": [], @@ -197,17 +197,18 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "dc076c56", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'How many breeds of dog are there?'" + "{'topic': 'dog',\n", + " 'text': 'What is the name of the dog that is the most popular in the world?'}" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -216,7 +217,7 @@ "from langchain.chains import LLMChain\n", "\n", "llm_chain = LLMChain(prompt=prompt, llm=watsonx_llm)\n", - "llm_chain.run(\"dog\")" + "llm_chain.invoke(\"dog\")" ] }, { @@ -248,7 +249,7 @@ "source": [ "# Calling a single prompt\n", "\n", - "watsonx_llm(\"Who is man's best friend?\")" + "watsonx_llm.invoke(\"Who is man's best friend?\")" ] }, { @@ -327,7 +328,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.18" + "version": "3.10.13" } }, "nbformat": 4, From f9be877ed72d5e74a12af14d0e69659f895cac80 Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Mon, 22 Jan 2024 16:24:28 +0100 Subject: [PATCH 131/215] Docs: Add self-querying retriever and store to AstraDB provider doc (#16362) Add self-querying retriever and store to AstraDB provider doc --- docs/docs/integrations/providers/astradb.mdx | 80 ++++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/docs/docs/integrations/providers/astradb.mdx b/docs/docs/integrations/providers/astradb.mdx index e4c69d0e42884..20a94d736b361 100644 --- a/docs/docs/integrations/providers/astradb.mdx +++ b/docs/docs/integrations/providers/astradb.mdx @@ -20,10 +20,10 @@ pip install "astrapy>=0.5.3" ```python from langchain_community.vectorstores import AstraDB vector_store = AstraDB( - embedding=my_embedding, - collection_name="my_store", - api_endpoint="...", - token="...", + embedding=my_embedding, + collection_name="my_store", + api_endpoint="...", + token="...", ) ``` @@ -40,7 +40,7 @@ set_llm_cache(AstraDBCache( )) ``` -Learn more in the [example notebook](/docs/integrations/llms/llm_caching) (scroll to the Astra DB section). +Learn more in the [example notebook](/docs/integrations/llms/llm_caching#astra-db-caches) (scroll to the Astra DB section). ### Semantic LLM Cache @@ -55,14 +55,14 @@ set_llm_cache(AstraDBSemanticCache( )) ``` -Learn more in the [example notebook](/docs/integrations/llms/llm_caching) (scroll to the appropriate section). +Learn more in the [example notebook](/docs/integrations/llms/llm_caching#astra-db-caches) (scroll to the appropriate section). ### Chat message history ```python from langchain.memory import AstraDBChatMessageHistory message_history = AstraDBChatMessageHistory( - session_id="test-session" + session_id="test-session", api_endpoint="...", token="...", ) @@ -75,14 +75,62 @@ Learn more in the [example notebook](/docs/integrations/memory/astradb_chat_mess ```python from langchain_community.document_loaders import AstraDBLoader loader = AstraDBLoader( + collection_name="my_collection", api_endpoint="...", - token="...", - collection_name="my_collection" + token="..." ) ``` Learn more in the [example notebook](/docs/integrations/document_loaders/astradb). +### Self-querying retriever + +```python +from langchain_community.vectorstores import AstraDB +from langchain.retrievers.self_query.base import SelfQueryRetriever + +vector_store = AstraDB( + embedding=my_embedding, + collection_name="my_store", + api_endpoint="...", + token="...", +) + +retriever = SelfQueryRetriever.from_llm( + my_llm, + vector_store, + document_content_description, + metadata_field_info +) +``` + +Learn more in the [example notebook](/docs/integrations/retrievers/self_query/astradb). + +### Store + +```python +from langchain_community.storage import AstraDBStore +store = AstraDBStore( + collection_name="my_kv_store", + api_endpoint="...", + token="..." +) +``` + +Learn more in the [example notebook](/docs/integrations/stores/astradb#astradbstore). + +### Byte Store + +```python +from langchain_community.storage import AstraDBByteStore +store = AstraDBByteStore( + collection_name="my_kv_store", + api_endpoint="...", + token="..." +) +``` + +Learn more in the [example notebook](/docs/integrations/stores/astradb#astradbbytestore). ## Apache Cassandra and Astra DB through CQL @@ -98,12 +146,12 @@ Hence, a different set of connectors, outlined below, shall be used. ```python from langchain_community.vectorstores import Cassandra vector_store = Cassandra( - embedding=my_embedding, - table_name="my_store", + embedding=my_embedding, + table_name="my_store", ) ``` -Learn more in the [example notebook](/docs/integrations/vectorstores/astradb) (scroll down to the CQL-specific section). +Learn more in the [example notebook](/docs/integrations/vectorstores/astradb#apache-cassandra-and-astra-db-through-cql) (scroll down to the CQL-specific section). ### Memory @@ -123,7 +171,7 @@ from langchain.cache import CassandraCache langchain.llm_cache = CassandraCache() ``` -Learn more in the [example notebook](/docs/integrations/llms/llm_caching) (scroll to the Cassandra section). +Learn more in the [example notebook](/docs/integrations/llms/llm_caching#cassandra-caches) (scroll to the Cassandra section). ### Semantic LLM Cache @@ -131,9 +179,9 @@ Learn more in the [example notebook](/docs/integrations/llms/llm_caching) (scrol ```python from langchain.cache import CassandraSemanticCache cassSemanticCache = CassandraSemanticCache( - embedding=my_embedding, - table_name="my_store", + embedding=my_embedding, + table_name="my_store", ) ``` -Learn more in the [example notebook](/docs/integrations/llms/llm_caching) (scroll to the appropriate section). +Learn more in the [example notebook](/docs/integrations/llms/llm_caching#cassandra-caches) (scroll to the appropriate section). From 971a68d04fe84c25c586c73a3eb559762c5af3a6 Mon Sep 17 00:00:00 2001 From: Nuno Campos <nuno@langchain.dev> Date: Mon, 22 Jan 2024 07:42:31 -0800 Subject: [PATCH 132/215] Docs: Update README.md in core (#16329) Docs: Update README.md in core --- libs/core/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/core/README.md b/libs/core/README.md index e110c443dfa87..7de5b5a7f1b66 100644 --- a/libs/core/README.md +++ b/libs/core/README.md @@ -35,6 +35,8 @@ You can use LangChain Core objects in two ways: 2. **declarative**, with LangChain Expression Language (LCEL) +3. or a mix of both! eg. one of the steps in your LCEL sequence can be a custom function + | Feature | Imperative | Declarative | | --------- | ------------------------------- | -------------- | | Syntax | All of Python | LCEL | From e1c59779ad50c0b6cf5ce6cbb85568e55bee6573 Mon Sep 17 00:00:00 2001 From: James Braza <jamesbraza@gmail.com> Date: Mon, 22 Jan 2024 07:48:54 -0800 Subject: [PATCH 133/215] core[patch]: Remove `print` statement on missing `grandalf` dependency in favor of more explicit ImportError (#16326) After this PR an ImportError will be raised without a print if grandalf is missing when using grandalf related code for printing runnable graphs. --- libs/core/langchain_core/runnables/graph_draw.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libs/core/langchain_core/runnables/graph_draw.py b/libs/core/langchain_core/runnables/graph_draw.py index be78c9c540181..87facfb92ea79 100644 --- a/libs/core/langchain_core/runnables/graph_draw.py +++ b/libs/core/langchain_core/runnables/graph_draw.py @@ -165,9 +165,11 @@ def _build_sugiyama_layout( EdgeViewer, route_with_lines, ) - except ImportError: - print("Install grandalf to draw graphs. `pip install grandalf`") - raise + except ImportError as exc: + raise ImportError( + "Install grandalf to draw graphs: `pip install grandalf`." + ) from exc + # # Just a reminder about naming conventions: # +------------X From acc14802d1220eb72ee1d9d3c6d4de77f835b95e Mon Sep 17 00:00:00 2001 From: Bob Lin <bob401710@gmail.com> Date: Mon, 22 Jan 2024 09:53:49 -0600 Subject: [PATCH 134/215] Fix `conn` field definition in SQLiteEntityStore (#15440) --- libs/langchain/langchain/memory/entity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/langchain/langchain/memory/entity.py b/libs/langchain/langchain/memory/entity.py index f2fb62ed9659e..65b31cf2d34dc 100644 --- a/libs/langchain/langchain/memory/entity.py +++ b/libs/langchain/langchain/memory/entity.py @@ -236,6 +236,12 @@ class SQLiteEntityStore(BaseEntityStore): session_id: str = "default" table_name: str = "memory_store" + conn: Any = None + + class Config: + """Configuration for this pydantic object.""" + + arbitrary_types_allowed = True def __init__( self, From 05162928c045ce4d43507e53d42433aca3cfe8a7 Mon Sep 17 00:00:00 2001 From: Jatin Chawda <38835306+jatinchawda1503@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:03:03 +0100 Subject: [PATCH 135/215] Docs: Fixed Urls of AsyncHtmlLoader, AsyncChromiumLoader and HTML2Text links in Web scraping Docs (#16365) Fixing links in documentation. --- docs/docs/use_cases/web_scraping.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/use_cases/web_scraping.ipynb b/docs/docs/use_cases/web_scraping.ipynb index 1478220d64a1f..19848f19d6aa6 100644 --- a/docs/docs/use_cases/web_scraping.ipynb +++ b/docs/docs/use_cases/web_scraping.ipynb @@ -144,11 +144,11 @@ "\n", "### AsyncHtmlLoader\n", "\n", - "The [AsyncHtmlLoader](docs/integrations/document_loaders/async_html) uses the `aiohttp` library to make asynchronous HTTP requests, suitable for simpler and lightweight scraping.\n", + "The [AsyncHtmlLoader](/docs/integrations/document_loaders/async_html) uses the `aiohttp` library to make asynchronous HTTP requests, suitable for simpler and lightweight scraping.\n", "\n", "### AsyncChromiumLoader\n", "\n", - "The [AsyncChromiumLoader](docs/integrations/document_loaders/async_chromium) uses Playwright to launch a Chromium instance, which can handle JavaScript rendering and more complex web interactions.\n", + "The [AsyncChromiumLoader](/docs/integrations/document_loaders/async_chromium) uses Playwright to launch a Chromium instance, which can handle JavaScript rendering and more complex web interactions.\n", "\n", "Chromium is one of the browsers supported by Playwright, a library used to control browser automation. \n", "\n", @@ -178,7 +178,7 @@ "\n", "### HTML2Text\n", "\n", - "[HTML2Text](docs/integrations/document_transformers/html2text) provides a straightforward conversion of HTML content into plain text (with markdown-like formatting) without any specific tag manipulation. \n", + "[HTML2Text](/docs/integrations/document_transformers/html2text) provides a straightforward conversion of HTML content into plain text (with markdown-like formatting) without any specific tag manipulation. \n", "\n", "It's best suited for scenarios where the goal is to extract human-readable text without needing to manipulate specific HTML elements.\n", "\n", From 1dc6c1ce06837948278938e6e603ff1ddddcce63 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:19:08 -0800 Subject: [PATCH 136/215] core[patch], community[patch], langchain[patch], docs: Update SQL chains/agents/docs (#16168) Revamp SQL use cases docs. In the process update SQL chains and agents. --- docs/docs/get_started/introduction.mdx | 2 +- docs/docs/get_started/quickstart.mdx | 2 +- .../integrations/providers/motherduck.mdx | 2 +- .../use_cases/qa_structured/_category_.yml | 2 - docs/docs/use_cases/qa_structured/sql.ipynb | 1259 ----------------- .../use_cases/question_answering/index.ipynb | 4 +- docs/docs/use_cases/sql/agents.ipynb | 815 +++++++++++ docs/docs/use_cases/sql/index.ipynb | 68 + docs/docs/use_cases/sql/large_db.ipynb | 627 ++++++++ docs/docs/use_cases/sql/prompting.ipynb | 789 +++++++++++ docs/docs/use_cases/sql/query_checking.ipynb | 389 +++++ docs/docs/use_cases/sql/quickstart.ipynb | 603 ++++++++ docs/vercel.json | 14 +- .../agent_toolkits/sql/base.py | 208 ++- .../agent_toolkits/sql/toolkit.py | 4 + .../utilities/sql_database.py | 12 +- .../example_selectors/semantic_similarity.py | 11 +- libs/langchain/langchain/agents/agent.py | 21 +- libs/langchain/langchain/agents/mrkl/base.py | 6 +- libs/langchain/langchain/chains/base.py | 4 +- .../langchain/chains/sql_database/query.py | 83 +- libs/langchain/langchain/tools/retriever.py | 52 +- 22 files changed, 3617 insertions(+), 1360 deletions(-) delete mode 100644 docs/docs/use_cases/qa_structured/_category_.yml delete mode 100644 docs/docs/use_cases/qa_structured/sql.ipynb create mode 100644 docs/docs/use_cases/sql/agents.ipynb create mode 100644 docs/docs/use_cases/sql/index.ipynb create mode 100644 docs/docs/use_cases/sql/large_db.ipynb create mode 100644 docs/docs/use_cases/sql/prompting.ipynb create mode 100644 docs/docs/use_cases/sql/query_checking.ipynb create mode 100644 docs/docs/use_cases/sql/quickstart.ipynb diff --git a/docs/docs/get_started/introduction.mdx b/docs/docs/get_started/introduction.mdx index d6219868e5415..4bf8b1fa9b590 100644 --- a/docs/docs/get_started/introduction.mdx +++ b/docs/docs/get_started/introduction.mdx @@ -78,7 +78,7 @@ Let models choose which tools to use given high-level directives Walkthroughs and techniques for common end-to-end use cases, like: - [Document question answering](/docs/use_cases/question_answering/) - [Chatbots](/docs/use_cases/chatbots/) -- [Analyzing structured data](/docs/use_cases/qa_structured/sql/) +- [Analyzing structured data](/docs/use_cases/sql/) - and much more... ### [Integrations](/docs/integrations/providers/) diff --git a/docs/docs/get_started/quickstart.mdx b/docs/docs/get_started/quickstart.mdx index 76b402138ef25..d07d3f2939ca3 100644 --- a/docs/docs/get_started/quickstart.mdx +++ b/docs/docs/get_started/quickstart.mdx @@ -597,6 +597,6 @@ To continue on your journey, we recommend you read the following (in order): - [Model IO](/docs/modules/model_io) covers more details of prompts, LLMs, and output parsers. - [Retrieval](/docs/modules/data_connection) covers more details of everything related to retrieval - [Agents](/docs/modules/agents) covers details of everything related to agents -- Explore common [end-to-end use cases](/docs/use_cases/qa_structured/sql) and [template applications](/docs/templates) +- Explore common [end-to-end use cases](/docs/use_cases/) and [template applications](/docs/templates) - [Read up on LangSmith](/docs/langsmith/), the platform for debugging, testing, monitoring and more - Learn more about serving your applications with [LangServe](/docs/langserve) diff --git a/docs/docs/integrations/providers/motherduck.mdx b/docs/docs/integrations/providers/motherduck.mdx index 5ef5c65e9cd0f..ee39a117bb291 100644 --- a/docs/docs/integrations/providers/motherduck.mdx +++ b/docs/docs/integrations/providers/motherduck.mdx @@ -33,7 +33,7 @@ db = SQLDatabase.from_uri(conn_str) db_chain = SQLDatabaseChain.from_llm(OpenAI(temperature=0), db, verbose=True) ``` -From here, see the [SQL Chain](/docs/use_cases/tabular/sqlite) documentation on how to use. +From here, see the [SQL Chain](/docs/use_cases/sql/) documentation on how to use. ## LLMCache diff --git a/docs/docs/use_cases/qa_structured/_category_.yml b/docs/docs/use_cases/qa_structured/_category_.yml deleted file mode 100644 index 4cae9a0c8db1c..0000000000000 --- a/docs/docs/use_cases/qa_structured/_category_.yml +++ /dev/null @@ -1,2 +0,0 @@ -label: 'Q&A over structured data' -position: 0.1 diff --git a/docs/docs/use_cases/qa_structured/sql.ipynb b/docs/docs/use_cases/qa_structured/sql.ipynb deleted file mode 100644 index e19bbe4ccd3f1..0000000000000 --- a/docs/docs/use_cases/qa_structured/sql.ipynb +++ /dev/null @@ -1,1259 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "title: SQL\n", - "sidebar_position: 2\n", - "---\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain/blob/master/docs/docs/use_cases/qa_structured/sql.ipynb)\n", - "\n", - "## Use case\n", - "\n", - "Enterprise data is often stored in SQL databases.\n", - "\n", - "LLMs make it possible to interact with SQL databases using natural language.\n", - "\n", - "LangChain offers SQL Chains and Agents to build and run SQL queries based on natural language prompts. \n", - "\n", - "These are compatible with any SQL dialect supported by SQLAlchemy (e.g., MySQL, PostgreSQL, Oracle SQL, Databricks, SQLite).\n", - "\n", - "They enable use cases such as:\n", - "\n", - "- Generating queries that will be run based on natural language questions\n", - "- Creating chatbots that can answer questions based on database data\n", - "- Building custom dashboards based on insights a user wants to analyze\n", - "\n", - "## Overview\n", - "\n", - "LangChain provides tools to interact with SQL Databases:\n", - "\n", - "1. `Build SQL queries` based on natural language user questions\n", - "2. `Query a SQL database` using chains for query creation and execution\n", - "3. `Interact with a SQL database` using agents for robust and flexible querying \n", - "\n", - "![sql_usecase.png](../../../static/img/sql_usecase.png)\n", - "\n", - "## Quickstart\n", - "\n", - "First, get required packages and set environment variables:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install --upgrade --quiet langchain langchain-experimental langchain-openai\n", - "\n", - "# Set env var OPENAI_API_KEY or load from a .env file\n", - "# import dotenv\n", - "\n", - "# dotenv.load_dotenv()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The below example will use a SQLite connection with Chinook database. \n", - " \n", - "Follow [installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", - "\n", - "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) to the directory as `Chinook_Sqlite.sql`\n", - "* Run `sqlite3 Chinook.db`\n", - "* Run `.read Chinook_Sqlite.sql`\n", - "* Test `SELECT * FROM Artist LIMIT 10;`\n", - "\n", - "Now, `Chinhook.db` is in our directory.\n", - "\n", - "Let's create a `SQLDatabaseChain` to create and execute SQL queries." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_community.utilities import SQLDatabase\n", - "from langchain_experimental.sql import SQLDatabaseChain\n", - "from langchain_openai import OpenAI\n", - "\n", - "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", - "llm = OpenAI(temperature=0, verbose=True)\n", - "db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", - "How many employees are there?\n", - "SQLQuery:\u001b[32;1m\u001b[1;3mSELECT COUNT(*) FROM \"Employee\";\u001b[0m\n", - "SQLResult: \u001b[33;1m\u001b[1;3m[(8,)]\u001b[0m\n", - "Answer:\u001b[32;1m\u001b[1;3mThere are 8 employees.\u001b[0m\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'There are 8 employees.'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db_chain.run(\"How many employees are there?\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that this both creates and executes the query. \n", - "\n", - "In the following sections, we will cover the 3 different use cases mentioned in the overview.\n", - "\n", - "### Go deeper\n", - "\n", - "You can load tabular data from other sources other than SQL Databases.\n", - "For example:\n", - "- [Loading a CSV file](/docs/integrations/document_loaders/csv)\n", - "- [Loading a Pandas DataFrame](/docs/integrations/document_loaders/pandas_dataframe)\n", - "Here you can [check full list of Document Loaders](/docs/integrations/document_loaders/)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case 1: Text-to-SQL query\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.chains import create_sql_query_chain\n", - "from langchain_openai import ChatOpenAI" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's create the chain that will build the SQL Query:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SELECT COUNT(*) FROM Employee\n" - ] - } - ], - "source": [ - "chain = create_sql_query_chain(ChatOpenAI(temperature=0), db)\n", - "response = chain.invoke({\"question\": \"How many employees are there\"})\n", - "print(response)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After building the SQL query based on a user question, we can execute the query:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'[(8,)]'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db.run(response)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the SQL Query Builder chain **only created** the query, and we handled the **query execution separately**." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Go deeper\n", - "\n", - "**Looking under the hood**\n", - "\n", - "We can look at the [LangSmith trace](https://smith.langchain.com/public/c8fa52ea-be46-4829-bde2-52894970b830/r) to unpack this:\n", - "\n", - "[Some papers](https://arxiv.org/pdf/2204.00498.pdf) have reported good performance when prompting with:\n", - " \n", - "* A `CREATE TABLE` description for each table, which include column names, their types, etc\n", - "* Followed by three example rows in a `SELECT` statement\n", - "\n", - "`create_sql_query_chain` adopts this the best practice (see more in this [blog](https://blog.langchain.dev/llms-and-sql/)). \n", - "![sql_usecase.png](../../../static/img/create_sql_query_chain.png)\n", - "\n", - "**Improvements**\n", - "\n", - "The query builder can be improved in several ways, such as (but not limited to):\n", - "\n", - "- Customizing database description to your specific use case\n", - "- Hardcoding a few examples of questions and their corresponding SQL query in the prompt\n", - "- Using a vector database to include dynamic examples that are relevant to the specific user question\n", - "\n", - "All these examples involve customizing the chain's prompt. \n", - "\n", - "For example, we can include a few examples in our prompt like so:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.prompts import PromptTemplate\n", - "\n", - "TEMPLATE = \"\"\"Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.\n", - "Use the following format:\n", - "\n", - "Question: \"Question here\"\n", - "SQLQuery: \"SQL Query to run\"\n", - "SQLResult: \"Result of the SQLQuery\"\n", - "Answer: \"Final answer here\"\n", - "\n", - "Only use the following tables:\n", - "\n", - "{table_info}.\n", - "\n", - "Some examples of SQL queries that correspond to questions are:\n", - "\n", - "{few_shot_examples}\n", - "\n", - "Question: {input}\"\"\"\n", - "\n", - "CUSTOM_PROMPT = PromptTemplate(\n", - " input_variables=[\"input\", \"few_shot_examples\", \"table_info\", \"dialect\"],\n", - " template=TEMPLATE,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also access this [prompt](https://smith.langchain.com/hub/rlm/text-to-sql) in the LangChain prompt hub.\n", - "\n", - "This will work with your [LangSmith API key](https://docs.smith.langchain.com/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain import hub\n", - "\n", - "CUSTOM_PROMPT = hub.pull(\"rlm/text-to-sql\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case 2: Text-to-SQL query and execution\n", - "\n", - "We can use `SQLDatabaseChain` from `langchain_experimental` to create and run SQL queries." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_experimental.sql import SQLDatabaseChain\n", - "from langchain_openai import OpenAI\n", - "\n", - "llm = OpenAI(temperature=0, verbose=True)\n", - "db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", - "How many employees are there?\n", - "SQLQuery:\u001b[32;1m\u001b[1;3mSELECT COUNT(*) FROM \"Employee\";\u001b[0m\n", - "SQLResult: \u001b[33;1m\u001b[1;3m[(8,)]\u001b[0m\n", - "Answer:\u001b[32;1m\u001b[1;3mThere are 8 employees.\u001b[0m\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'There are 8 employees.'" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db_chain.run(\"How many employees are there?\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, we get the same result as the previous case.\n", - "\n", - "Here, the chain **also handles the query execution** and provides a final answer based on the user question and the query result.\n", - "\n", - "**Be careful** while using this approach as it is susceptible to `SQL Injection`:\n", - "\n", - "* The chain is executing queries that are created by an LLM, and weren't validated\n", - "* e.g. records may be created, modified or deleted unintentionally_\n", - "\n", - "This is why we see the `SQLDatabaseChain` is inside `langchain_experimental`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Go deeper\n", - "\n", - "**Looking under the hood**\n", - "\n", - "We can use the [LangSmith trace](https://smith.langchain.com/public/7f202a0c-1e35-42d6-a84a-6c2a58f697ef/r) to see what is happening under the hood:\n", - "\n", - "* As discussed above, first we create the query:\n", - "\n", - "```\n", - "text: ' SELECT COUNT(*) FROM \"Employee\";'\n", - "```\n", - "\n", - "* Then, it executes the query and passes the results to an LLM for synthesis.\n", - "\n", - "![sql_usecase.png](../../../static/img/sqldbchain_trace.png)\n", - "\n", - "\n", - "**Adding Sample Rows**\n", - "\n", - "Providing sample data can help the LLM construct correct queries when the data format is not obvious. \n", - "\n", - "For example, we can tell LLM that artists are saved with their full names by providing two rows from the Track table.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "db = SQLDatabase.from_uri(\n", - " \"sqlite:///Chinook.db\",\n", - " include_tables=[\n", - " \"Track\"\n", - " ], # we include only one table to save tokens in the prompt :)\n", - " sample_rows_in_table_info=2,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The sample rows are added to the prompt after each corresponding table's column information.\n", - "\n", - "We can use `db.table_info` and check which sample rows are included:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "CREATE TABLE \"Track\" (\n", - "\t\"TrackId\" INTEGER NOT NULL, \n", - "\t\"Name\" NVARCHAR(200) NOT NULL, \n", - "\t\"AlbumId\" INTEGER, \n", - "\t\"MediaTypeId\" INTEGER NOT NULL, \n", - "\t\"GenreId\" INTEGER, \n", - "\t\"Composer\" NVARCHAR(220), \n", - "\t\"Milliseconds\" INTEGER NOT NULL, \n", - "\t\"Bytes\" INTEGER, \n", - "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", - "\tPRIMARY KEY (\"TrackId\"), \n", - "\tFOREIGN KEY(\"MediaTypeId\") REFERENCES \"MediaType\" (\"MediaTypeId\"), \n", - "\tFOREIGN KEY(\"GenreId\") REFERENCES \"Genre\" (\"GenreId\"), \n", - "\tFOREIGN KEY(\"AlbumId\") REFERENCES \"Album\" (\"AlbumId\")\n", - ")\n", - "\n", - "/*\n", - "2 rows from Track table:\n", - "TrackId\tName\tAlbumId\tMediaTypeId\tGenreId\tComposer\tMilliseconds\tBytes\tUnitPrice\n", - "1\tFor Those About To Rock (We Salute You)\t1\t1\t1\tAngus Young, Malcolm Young, Brian Johnson\t343719\t11170334\t0.99\n", - "2\tBalls to the Wall\t2\t2\t1\tNone\t342562\t5510424\t0.99\n", - "*/\n" - ] - } - ], - "source": [ - "print(db.table_info)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case 3: SQL agents\n", - "\n", - "LangChain has an SQL Agent which provides a more flexible way of interacting with SQL Databases than the `SQLDatabaseChain`.\n", - "\n", - "The main advantages of using the SQL Agent are:\n", - "\n", - "- It can answer questions based on the databases' schema as well as on the databases' content (like describing a specific table)\n", - "- It can recover from errors by running a generated query, catching the traceback and regenerating it correctly\n", - "\n", - "To initialize the agent, we use `create_sql_agent` function. \n", - "\n", - "This agent contains the `SQLDatabaseToolkit` which contains tools to: \n", - "\n", - "* Create and execute queries\n", - "* Check query syntax\n", - "* Retrieve table descriptions\n", - "* ... and more" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_sql_agent\n", - "\n", - "# from langchain.agents import AgentExecutor\n", - "from langchain.agents.agent_types import AgentType\n", - "from langchain_community.agent_toolkits import SQLDatabaseToolkit\n", - "\n", - "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", - "\n", - "agent_executor = create_sql_agent(\n", - " llm=OpenAI(temperature=0),\n", - " toolkit=SQLDatabaseToolkit(db=db, llm=OpenAI(temperature=0)),\n", - " verbose=True,\n", - " agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Agent task example #1 - Running queries\n" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: sql_db_list_tables\n", - "Action Input: \u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should query the schema of the Invoice and Customer tables.\n", - "Action: sql_db_schema\n", - "Action Input: Invoice, Customer\u001b[0m\n", - "Observation: \u001b[33;1m\u001b[1;3m\n", - "CREATE TABLE \"Customer\" (\n", - "\t\"CustomerId\" INTEGER NOT NULL, \n", - "\t\"FirstName\" NVARCHAR(40) NOT NULL, \n", - "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", - "\t\"Company\" NVARCHAR(80), \n", - "\t\"Address\" NVARCHAR(70), \n", - "\t\"City\" NVARCHAR(40), \n", - "\t\"State\" NVARCHAR(40), \n", - "\t\"Country\" NVARCHAR(40), \n", - "\t\"PostalCode\" NVARCHAR(10), \n", - "\t\"Phone\" NVARCHAR(24), \n", - "\t\"Fax\" NVARCHAR(24), \n", - "\t\"Email\" NVARCHAR(60) NOT NULL, \n", - "\t\"SupportRepId\" INTEGER, \n", - "\tPRIMARY KEY (\"CustomerId\"), \n", - "\tFOREIGN KEY(\"SupportRepId\") REFERENCES \"Employee\" (\"EmployeeId\")\n", - ")\n", - "\n", - "/*\n", - "3 rows from Customer table:\n", - "CustomerId\tFirstName\tLastName\tCompany\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\tSupportRepId\n", - "1\tLuís\tGonçalves\tEmbraer - Empresa Brasileira de Aeronáutica S.A.\tAv. Brigadeiro Faria Lima, 2170\tSão José dos Campos\tSP\tBrazil\t12227-000\t+55 (12) 3923-5555\t+55 (12) 3923-5566\tluisg@embraer.com.br\t3\n", - "2\tLeonie\tKöhler\tNone\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t+49 0711 2842222\tNone\tleonekohler@surfeu.de\t5\n", - "3\tFrançois\tTremblay\tNone\t1498 rue Bélanger\tMontréal\tQC\tCanada\tH2G 1A7\t+1 (514) 721-4711\tNone\tftremblay@gmail.com\t3\n", - "*/\n", - "\n", - "\n", - "CREATE TABLE \"Invoice\" (\n", - "\t\"InvoiceId\" INTEGER NOT NULL, \n", - "\t\"CustomerId\" INTEGER NOT NULL, \n", - "\t\"InvoiceDate\" DATETIME NOT NULL, \n", - "\t\"BillingAddress\" NVARCHAR(70), \n", - "\t\"BillingCity\" NVARCHAR(40), \n", - "\t\"BillingState\" NVARCHAR(40), \n", - "\t\"BillingCountry\" NVARCHAR(40), \n", - "\t\"BillingPostalCode\" NVARCHAR(10), \n", - "\t\"Total\" NUMERIC(10, 2) NOT NULL, \n", - "\tPRIMARY KEY (\"InvoiceId\"), \n", - "\tFOREIGN KEY(\"CustomerId\") REFERENCES \"Customer\" (\"CustomerId\")\n", - ")\n", - "\n", - "/*\n", - "3 rows from Invoice table:\n", - "InvoiceId\tCustomerId\tInvoiceDate\tBillingAddress\tBillingCity\tBillingState\tBillingCountry\tBillingPostalCode\tTotal\n", - "1\t2\t2009-01-01 00:00:00\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t1.98\n", - "2\t4\t2009-01-02 00:00:00\tUllevålsveien 14\tOslo\tNone\tNorway\t0171\t3.96\n", - "3\t8\t2009-01-03 00:00:00\tGrétrystraat 63\tBrussels\tNone\tBelgium\t1000\t5.94\n", - "*/\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should query the total sales per country.\n", - "Action: sql_db_query\n", - "Action Input: SELECT Country, SUM(Total) AS TotalSales FROM Invoice INNER JOIN Customer ON Invoice.CustomerId = Customer.CustomerId GROUP BY Country ORDER BY TotalSales DESC LIMIT 10\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3m[('USA', 523.0600000000003), ('Canada', 303.9599999999999), ('France', 195.09999999999994), ('Brazil', 190.09999999999997), ('Germany', 156.48), ('United Kingdom', 112.85999999999999), ('Czech Republic', 90.24000000000001), ('Portugal', 77.23999999999998), ('India', 75.25999999999999), ('Chile', 46.62)]\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: The country with the highest total sales is the USA, with a total of $523.06.\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'The country with the highest total sales is the USA, with a total of $523.06.'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "agent_executor.run(\n", - " \"List the total sales per country. Which country's customers spent the most?\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Looking at the [LangSmith trace](https://smith.langchain.com/public/a86dbe17-5782-4020-bce6-2de85343168a/r), we can see:\n", - "\n", - "* The agent is using a ReAct style prompt\n", - "* First, it will look at the tables: `Action: sql_db_list_tables` using tool `sql_db_list_tables`\n", - "* Given the tables as an observation, it `thinks` and then determinates the next `action`:\n", - "\n", - "```\n", - "Observation: Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\n", - "Thought: I should query the schema of the Invoice and Customer tables.\n", - "Action: sql_db_schema\n", - "Action Input: Invoice, Customer\n", - "```\n", - "\n", - "* It then formulates the query using the schema from tool `sql_db_schema`\n", - "\n", - "```\n", - "Thought: I should query the total sales per country.\n", - "Action: sql_db_query\n", - "Action Input: SELECT Country, SUM(Total) AS TotalSales FROM Invoice INNER JOIN Customer ON Invoice.CustomerId = Customer.CustomerId GROUP BY Country ORDER BY TotalSales DESC LIMIT 10\n", - "```\n", - "\n", - "* It finally executes the generated query using tool `sql_db_query`\n", - "\n", - "![sql_usecase.png](../../../static/img/SQLDatabaseToolkit.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Agent task example #2 - Describing a Table" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: sql_db_list_tables\n", - "Action Input: \u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m The PlaylistTrack table is the most relevant to the question.\n", - "Action: sql_db_schema\n", - "Action Input: PlaylistTrack\u001b[0m\n", - "Observation: \u001b[33;1m\u001b[1;3m\n", - "CREATE TABLE \"PlaylistTrack\" (\n", - "\t\"PlaylistId\" INTEGER NOT NULL, \n", - "\t\"TrackId\" INTEGER NOT NULL, \n", - "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", - "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", - "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", - ")\n", - "\n", - "/*\n", - "3 rows from PlaylistTrack table:\n", - "PlaylistId\tTrackId\n", - "1\t3402\n", - "1\t3389\n", - "1\t3390\n", - "*/\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: The PlaylistTrack table contains two columns, PlaylistId and TrackId, which are both integers and form a primary key. It also has two foreign keys, one to the Track table and one to the Playlist table.\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'The PlaylistTrack table contains two columns, PlaylistId and TrackId, which are both integers and form a primary key. It also has two foreign keys, one to the Track table and one to the Playlist table.'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "agent_executor.run(\"Describe the playlisttrack table\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Extending the SQL Toolkit\n", - "\n", - "Although the out-of-the-box SQL Toolkit contains the necessary tools to start working on a database, it is often the case that some extra tools may be useful for extending the agent's capabilities. This is particularly useful when trying to use **domain specific knowledge** in the solution, in order to improve its overall performance.\n", - "\n", - "Some examples include:\n", - "\n", - "- Including dynamic few shot examples\n", - "- Finding misspellings in proper nouns to use as column filters\n", - "\n", - "We can create separate tools which tackle these specific use cases and include them as a complement to the standard SQL Toolkit. Let's see how to include these two custom tools.\n", - "\n", - "#### Including dynamic few-shot examples\n", - "\n", - "In order to include dynamic few-shot examples, we need a custom **Retriever Tool** that handles the vector database in order to retrieve the examples that are semantically similar to the user’s question.\n", - "\n", - "Let's start by creating a dictionary with some examples: " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "few_shots = {\n", - " \"List all artists.\": \"SELECT * FROM artists;\",\n", - " \"Find all albums for the artist 'AC/DC'.\": \"SELECT * FROM albums WHERE ArtistId = (SELECT ArtistId FROM artists WHERE Name = 'AC/DC');\",\n", - " \"List all tracks in the 'Rock' genre.\": \"SELECT * FROM tracks WHERE GenreId = (SELECT GenreId FROM genres WHERE Name = 'Rock');\",\n", - " \"Find the total duration of all tracks.\": \"SELECT SUM(Milliseconds) FROM tracks;\",\n", - " \"List all customers from Canada.\": \"SELECT * FROM customers WHERE Country = 'Canada';\",\n", - " \"How many tracks are there in the album with ID 5?\": \"SELECT COUNT(*) FROM tracks WHERE AlbumId = 5;\",\n", - " \"Find the total number of invoices.\": \"SELECT COUNT(*) FROM invoices;\",\n", - " \"List all tracks that are longer than 5 minutes.\": \"SELECT * FROM tracks WHERE Milliseconds > 300000;\",\n", - " \"Who are the top 5 customers by total purchase?\": \"SELECT CustomerId, SUM(Total) AS TotalPurchase FROM invoices GROUP BY CustomerId ORDER BY TotalPurchase DESC LIMIT 5;\",\n", - " \"Which albums are from the year 2000?\": \"SELECT * FROM albums WHERE strftime('%Y', ReleaseDate) = '2000';\",\n", - " \"How many employees are there\": 'SELECT COUNT(*) FROM \"employee\"',\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can then create a retriever using the list of questions, assigning the target SQL query as metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.schema import Document\n", - "from langchain_community.vectorstores import FAISS\n", - "from langchain_openai import OpenAIEmbeddings\n", - "\n", - "embeddings = OpenAIEmbeddings()\n", - "\n", - "few_shot_docs = [\n", - " Document(page_content=question, metadata={\"sql_query\": few_shots[question]})\n", - " for question in few_shots.keys()\n", - "]\n", - "vector_db = FAISS.from_documents(few_shot_docs, embeddings)\n", - "retriever = vector_db.as_retriever()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can create our own custom tool and append it as a new tool in the `create_sql_agent` function:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_community.agent_toolkits import create_retriever_tool\n", - "\n", - "tool_description = \"\"\"\n", - "This tool will help you understand similar examples to adapt them to the user question.\n", - "Input to this tool should be the user question.\n", - "\"\"\"\n", - "\n", - "retriever_tool = create_retriever_tool(\n", - " retriever, name=\"sql_get_similar_examples\", description=tool_description\n", - ")\n", - "custom_tool_list = [retriever_tool]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can create the agent, adjusting the standard SQL Agent suffix to consider our use case. Although the most straightforward way to handle this would be to include it just in the tool description, this is often not enough and we need to specify it in the agent prompt using the `suffix` argument in the constructor." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import AgentType, create_sql_agent\n", - "from langchain_community.agent_toolkits import SQLDatabaseToolkit\n", - "from langchain_community.utilities import SQLDatabase\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", - "llm = ChatOpenAI(model_name=\"gpt-4\", temperature=0)\n", - "\n", - "toolkit = SQLDatabaseToolkit(db=db, llm=llm)\n", - "\n", - "custom_suffix = \"\"\"\n", - "I should first get the similar examples I know.\n", - "If the examples are enough to construct the query, I can build it.\n", - "Otherwise, I can then look at the tables in the database to see what I can query.\n", - "Then I should query the schema of the most relevant tables\n", - "\"\"\"\n", - "\n", - "agent = create_sql_agent(\n", - " llm=llm,\n", - " toolkit=toolkit,\n", - " verbose=True,\n", - " agent_type=AgentType.OPENAI_FUNCTIONS,\n", - " extra_tools=custom_tool_list,\n", - " suffix=custom_suffix,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try it out:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_get_similar_examples` with `How many employees do we have?`\n", - "\n", - "\n", - "\u001b[0m\u001b[33;1m\u001b[1;3m[Document(page_content='How many employees are there', metadata={'sql_query': 'SELECT COUNT(*) FROM \"employee\"'}), Document(page_content='Find the total number of invoices.', metadata={'sql_query': 'SELECT COUNT(*) FROM invoices;'})]\u001b[0m\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_db_query_checker` with `SELECT COUNT(*) FROM employee`\n", - "responded: {content}\n", - "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3mSELECT COUNT(*) FROM employee\u001b[0m\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_db_query` with `SELECT COUNT(*) FROM employee`\n", - "\n", - "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3m[(8,)]\u001b[0m\u001b[32;1m\u001b[1;3mWe have 8 employees.\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'We have 8 employees.'" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "agent.run(\"How many employees do we have?\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the agent first used the `sql_get_similar_examples` tool in order to retrieve similar examples. As the question was very similar to other few shot examples, the agent **didn't need to use any other tool** from the standard Toolkit, thus **saving time and tokens**." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Finding and correcting misspellings for proper nouns\n", - "\n", - "In order to filter columns that contain proper nouns such as addresses, song names or artists, we first need to double-check the spelling in order to filter the data correctly. \n", - "\n", - "We can achieve this by creating a vector store using all the distinct proper nouns that exist in the database. We can then have the agent query that vector store each time the user includes a proper noun in their question, to find the correct spelling for that word. In this way, the agent can make sure it understands which entity the user is referring to before building the target query.\n", - "\n", - "Let's follow a similar approach to the few shots, but without metadata: just embedding the proper nouns and then querying to get the most similar one to the misspelled user question.\n", - "\n", - "First we need the unique values for each entity we want, for which we define a function that parses the result into a list of elements:" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [], - "source": [ - "import ast\n", - "import re\n", - "\n", - "\n", - "def run_query_save_results(db, query):\n", - " res = db.run(query)\n", - " res = [el for sub in ast.literal_eval(res) for el in sub if el]\n", - " res = [re.sub(r\"\\b\\d+\\b\", \"\", string).strip() for string in res]\n", - " return res\n", - "\n", - "\n", - "artists = run_query_save_results(db, \"SELECT Name FROM Artist\")\n", - "albums = run_query_save_results(db, \"SELECT Title FROM Album\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can proceed with creating the custom **retriever tool** and the final agent:" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_community.agent_toolkits import create_retriever_tool\n", - "from langchain_community.vectorstores import FAISS\n", - "from langchain_openai import OpenAIEmbeddings\n", - "\n", - "texts = artists + albums\n", - "\n", - "embeddings = OpenAIEmbeddings()\n", - "vector_db = FAISS.from_texts(texts, embeddings)\n", - "retriever = vector_db.as_retriever()\n", - "\n", - "retriever_tool = create_retriever_tool(\n", - " retriever,\n", - " name=\"name_search\",\n", - " description=\"use to learn how a piece of data is actually written, can be from names, surnames addresses etc\",\n", - ")\n", - "\n", - "custom_tool_list = [retriever_tool]" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import AgentType, create_sql_agent\n", - "from langchain_community.agent_toolkits import SQLDatabaseToolkit\n", - "from langchain_community.utilities import SQLDatabase\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "# db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", - "llm = ChatOpenAI(model_name=\"gpt-4\", temperature=0)\n", - "\n", - "toolkit = SQLDatabaseToolkit(db=db, llm=llm)\n", - "\n", - "custom_suffix = \"\"\"\n", - "If a user asks for me to filter based on proper nouns, I should first check the spelling using the name_search tool.\n", - "Otherwise, I can then look at the tables in the database to see what I can query.\n", - "Then I should query the schema of the most relevant tables\n", - "\"\"\"\n", - "\n", - "agent = create_sql_agent(\n", - " llm=llm,\n", - " toolkit=toolkit,\n", - " verbose=True,\n", - " agent_type=AgentType.OPENAI_FUNCTIONS,\n", - " extra_tools=custom_tool_list,\n", - " suffix=custom_suffix,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try it out:" - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3m\n", - "Invoking: `name_search` with `alis in pains`\n", - "\n", - "\n", - "\u001b[0m\u001b[33;1m\u001b[1;3m[Document(page_content='House of Pain', metadata={}), Document(page_content='Alice In Chains', metadata={}), Document(page_content='Aisha Duo', metadata={}), Document(page_content='House Of Pain', metadata={})]\u001b[0m\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_db_list_tables` with ``\n", - "responded: {content}\n", - "\n", - "\u001b[0m\u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_db_schema` with `Album, Artist`\n", - "responded: {content}\n", - "\n", - "\u001b[0m\u001b[33;1m\u001b[1;3m\n", - "CREATE TABLE \"Album\" (\n", - "\t\"AlbumId\" INTEGER NOT NULL, \n", - "\t\"Title\" NVARCHAR(160) NOT NULL, \n", - "\t\"ArtistId\" INTEGER NOT NULL, \n", - "\tPRIMARY KEY (\"AlbumId\"), \n", - "\tFOREIGN KEY(\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\")\n", - ")\n", - "\n", - "/*\n", - "3 rows from Album table:\n", - "AlbumId\tTitle\tArtistId\n", - "1\tFor Those About To Rock We Salute You\t1\n", - "2\tBalls to the Wall\t2\n", - "3\tRestless and Wild\t2\n", - "*/\n", - "\n", - "\n", - "CREATE TABLE \"Artist\" (\n", - "\t\"ArtistId\" INTEGER NOT NULL, \n", - "\t\"Name\" NVARCHAR(120), \n", - "\tPRIMARY KEY (\"ArtistId\")\n", - ")\n", - "\n", - "/*\n", - "3 rows from Artist table:\n", - "ArtistId\tName\n", - "1\tAC/DC\n", - "2\tAccept\n", - "3\tAerosmith\n", - "*/\u001b[0m\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_db_query_checker` with `SELECT COUNT(*) FROM Album JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name = 'Alice In Chains'`\n", - "responded: {content}\n", - "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3mSELECT COUNT(*) FROM Album JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name = 'Alice In Chains'\u001b[0m\u001b[32;1m\u001b[1;3m\n", - "Invoking: `sql_db_query` with `SELECT COUNT(*) FROM Album JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name = 'Alice In Chains'`\n", - "\n", - "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3m[(1,)]\u001b[0m\u001b[32;1m\u001b[1;3mAlice In Chains has 1 album in the database.\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'Alice In Chains has 1 album in the database.'" - ] - }, - "execution_count": 55, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "agent.run(\"How many albums does alis in pains have?\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the agent used the `name_search` tool in order to check how to correctly query the database for this specific artist." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Go deeper\n", - "\n", - "To learn more about the SQL Agent and how it works we refer to the [SQL Agent Toolkit](/docs/integrations/toolkits/sql_database) documentation.\n", - "\n", - "You can also check Agents for other document types:\n", - "- [Pandas Agent](/docs/integrations/toolkits/pandas)\n", - "- [CSV Agent](/docs/integrations/toolkits/csv)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Elastic Search\n", - "\n", - "Going beyond the above use-case, there are integrations with other databases.\n", - "\n", - "For example, we can interact with Elasticsearch analytics database. \n", - "\n", - "This chain builds search queries via the Elasticsearch DSL API (filters and aggregations).\n", - "\n", - "The Elasticsearch client must have permissions for index listing, mapping description and search queries.\n", - "\n", - "See [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html) for instructions on how to run Elasticsearch locally.\n", - "\n", - "Make sure to install the Elasticsearch Python client before:\n", - "\n", - "```sh\n", - "pip install elasticsearch\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "from elasticsearch import Elasticsearch\n", - "from langchain.chains.elasticsearch_database import ElasticsearchDatabaseChain\n", - "from langchain_openai import ChatOpenAI" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Initialize Elasticsearch python client.\n", - "# See https://elasticsearch-py.readthedocs.io/en/v8.8.2/api.html#elasticsearch.Elasticsearch\n", - "ELASTIC_SEARCH_SERVER = \"https://elastic:pass@localhost:9200\"\n", - "db = Elasticsearch(ELASTIC_SEARCH_SERVER)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Uncomment the next cell to initially populate your db." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# customers = [\n", - "# {\"firstname\": \"Jennifer\", \"lastname\": \"Walters\"},\n", - "# {\"firstname\": \"Monica\",\"lastname\":\"Rambeau\"},\n", - "# {\"firstname\": \"Carol\",\"lastname\":\"Danvers\"},\n", - "# {\"firstname\": \"Wanda\",\"lastname\":\"Maximoff\"},\n", - "# {\"firstname\": \"Jennifer\",\"lastname\":\"Takeda\"},\n", - "# ]\n", - "# for i, customer in enumerate(customers):\n", - "# db.create(index=\"customers\", document=customer, id=i)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "llm = ChatOpenAI(model_name=\"gpt-4\", temperature=0)\n", - "chain = ElasticsearchDatabaseChain.from_llm(llm=llm, database=db, verbose=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "question = \"What are the first names of all the customers?\"\n", - "chain.run(question)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can customize the prompt." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.prompts.prompt import PromptTemplate\n", - "\n", - "PROMPT_TEMPLATE = \"\"\"Given an input question, create a syntactically correct Elasticsearch query to run. Unless the user specifies in their question a specific number of examples they wish to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database.\n", - "\n", - "Unless told to do not query for all the columns from a specific index, only ask for a the few relevant columns given the question.\n", - "\n", - "Pay attention to use only the column names that you can see in the mapping description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which index. Return the query as valid json.\n", - "\n", - "Use the following format:\n", - "\n", - "Question: Question here\n", - "ESQuery: Elasticsearch Query formatted as json\n", - "\"\"\"\n", - "\n", - "PROMPT = PromptTemplate.from_template(\n", - " PROMPT_TEMPLATE,\n", - ")\n", - "chain = ElasticsearchDatabaseChain.from_llm(llm=llm, database=db, query_prompt=PROMPT)" - ] - } - ], - "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.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/docs/use_cases/question_answering/index.ipynb b/docs/docs/use_cases/question_answering/index.ipynb index 078b004c01aca..af23b8e3aa0ef 100644 --- a/docs/docs/use_cases/question_answering/index.ipynb +++ b/docs/docs/use_cases/question_answering/index.ipynb @@ -38,7 +38,7 @@ "\n", "**Note**: Here we focus on Q&A for unstructured data. Two RAG use cases which we cover elsewhere are:\n", "\n", - "- [Q&A over structured data](/docs/use_cases/qa_structured/sql) (e.g., SQL)\n", + "- [Q&A over SQL data](/docs/use_cases/sql/)\n", "- [Q&A over code](/docs/use_cases/code_understanding) (e.g., Python)" ] }, @@ -103,7 +103,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.9.1" } }, "nbformat": 4, diff --git a/docs/docs/use_cases/sql/agents.ipynb b/docs/docs/use_cases/sql/agents.ipynb new file mode 100644 index 0000000000000..aa6db0dd3f920 --- /dev/null +++ b/docs/docs/use_cases/sql/agents.ipynb @@ -0,0 +1,815 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 1\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Agents\n", + "\n", + "LangChain has a SQL Agent which provides a more flexible way of interacting with SQL Databases than a chain. The main advantages of using the SQL Agent are:\n", + "\n", + "- It can answer questions based on the databases' schema as well as on the databases' content (like describing a specific table).\n", + "- It can recover from errors by running a generated query, catching the traceback and regenerating it correctly.\n", + "- It can query the database as many times as needed to answer the user question.\n", + "\n", + "To initialize the agent we'll use the [create_sql_agent](https://api.python.langchain.com/en/latest/agent_toolkits/langchain_community.agent_toolkits.sql.base.create_sql_agent.html) constructor. This agent uses the `SQLDatabaseToolkit` which contains tools to: \n", + "\n", + "* Create and execute queries\n", + "* Check query syntax\n", + "* Retrieve table descriptions\n", + "* ... and more\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-openai" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Uncomment the below to use LangSmith. Not required.\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven `SQLDatabase` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\"" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM Artist LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Agent\n", + "\n", + "We'll use an OpenAI chat model and an `\"openai-tools\"` agent, which will use OpenAI's function-calling API to drive the agent's tool selection and invocations." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.agent_toolkits import create_sql_agent\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "agent_executor = create_sql_agent(llm, db=db, agent_type=\"openai-tools\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_list_tables` with `{}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_schema` with `Invoice,Customer`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m\n", + "CREATE TABLE \"Customer\" (\n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"FirstName\" NVARCHAR(40) NOT NULL, \n", + "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", + "\t\"Company\" NVARCHAR(80), \n", + "\t\"Address\" NVARCHAR(70), \n", + "\t\"City\" NVARCHAR(40), \n", + "\t\"State\" NVARCHAR(40), \n", + "\t\"Country\" NVARCHAR(40), \n", + "\t\"PostalCode\" NVARCHAR(10), \n", + "\t\"Phone\" NVARCHAR(24), \n", + "\t\"Fax\" NVARCHAR(24), \n", + "\t\"Email\" NVARCHAR(60) NOT NULL, \n", + "\t\"SupportRepId\" INTEGER, \n", + "\tPRIMARY KEY (\"CustomerId\"), \n", + "\tFOREIGN KEY(\"SupportRepId\") REFERENCES \"Employee\" (\"EmployeeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Customer table:\n", + "CustomerId\tFirstName\tLastName\tCompany\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\tSupportRepId\n", + "1\tLuís\tGonçalves\tEmbraer - Empresa Brasileira de Aeronáutica S.A.\tAv. Brigadeiro Faria Lima, 2170\tSão José dos Campos\tSP\tBrazil\t12227-000\t+55 (12) 3923-5555\t+55 (12) 3923-5566\tluisg@embraer.com.br\t3\n", + "2\tLeonie\tKöhler\tNone\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t+49 0711 2842222\tNone\tleonekohler@surfeu.de\t5\n", + "3\tFrançois\tTremblay\tNone\t1498 rue Bélanger\tMontréal\tQC\tCanada\tH2G 1A7\t+1 (514) 721-4711\tNone\tftremblay@gmail.com\t3\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Invoice\" (\n", + "\t\"InvoiceId\" INTEGER NOT NULL, \n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"InvoiceDate\" DATETIME NOT NULL, \n", + "\t\"BillingAddress\" NVARCHAR(70), \n", + "\t\"BillingCity\" NVARCHAR(40), \n", + "\t\"BillingState\" NVARCHAR(40), \n", + "\t\"BillingCountry\" NVARCHAR(40), \n", + "\t\"BillingPostalCode\" NVARCHAR(10), \n", + "\t\"Total\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"InvoiceId\"), \n", + "\tFOREIGN KEY(\"CustomerId\") REFERENCES \"Customer\" (\"CustomerId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Invoice table:\n", + "InvoiceId\tCustomerId\tInvoiceDate\tBillingAddress\tBillingCity\tBillingState\tBillingCountry\tBillingPostalCode\tTotal\n", + "1\t2\t2009-01-01 00:00:00\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t1.98\n", + "2\t4\t2009-01-02 00:00:00\tUllevålsveien 14\tOslo\tNone\tNorway\t0171\t3.96\n", + "3\t8\t2009-01-03 00:00:00\tGrétrystraat 63\tBrussels\tNone\tBelgium\t1000\t5.94\n", + "*/\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_query` with `SELECT c.Country, SUM(i.Total) AS TotalSales FROM Invoice i JOIN Customer c ON i.CustomerId = c.CustomerId GROUP BY c.Country ORDER BY TotalSales DESC`\n", + "responded: To list the total sales per country, I can query the \"Invoice\" and \"Customer\" tables. I will join these tables on the \"CustomerId\" column and group the results by the \"BillingCountry\" column. Then, I will calculate the sum of the \"Total\" column to get the total sales per country. Finally, I will order the results in descending order of the total sales.\n", + "\n", + "Here is the SQL query:\n", + "\n", + "```sql\n", + "SELECT c.Country, SUM(i.Total) AS TotalSales\n", + "FROM Invoice i\n", + "JOIN Customer c ON i.CustomerId = c.CustomerId\n", + "GROUP BY c.Country\n", + "ORDER BY TotalSales DESC\n", + "```\n", + "\n", + "Now, I will execute this query to get the results.\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[('USA', 523.0600000000003), ('Canada', 303.9599999999999), ('France', 195.09999999999994), ('Brazil', 190.09999999999997), ('Germany', 156.48), ('United Kingdom', 112.85999999999999), ('Czech Republic', 90.24000000000001), ('Portugal', 77.23999999999998), ('India', 75.25999999999999), ('Chile', 46.62), ('Ireland', 45.62), ('Hungary', 45.62), ('Austria', 42.62), ('Finland', 41.620000000000005), ('Netherlands', 40.62), ('Norway', 39.62), ('Sweden', 38.620000000000005), ('Poland', 37.620000000000005), ('Italy', 37.620000000000005), ('Denmark', 37.620000000000005), ('Australia', 37.620000000000005), ('Argentina', 37.620000000000005), ('Spain', 37.62), ('Belgium', 37.62)]\u001b[0m\u001b[32;1m\u001b[1;3mThe total sales per country are as follows:\n", + "\n", + "1. USA: $523.06\n", + "2. Canada: $303.96\n", + "3. France: $195.10\n", + "4. Brazil: $190.10\n", + "5. Germany: $156.48\n", + "6. United Kingdom: $112.86\n", + "7. Czech Republic: $90.24\n", + "8. Portugal: $77.24\n", + "9. India: $75.26\n", + "10. Chile: $46.62\n", + "\n", + "The country whose customers spent the most is the USA, with a total sales of $523.06.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': \"List the total sales per country. Which country's customers spent the most?\",\n", + " 'output': 'The total sales per country are as follows:\\n\\n1. USA: $523.06\\n2. Canada: $303.96\\n3. France: $195.10\\n4. Brazil: $190.10\\n5. Germany: $156.48\\n6. United Kingdom: $112.86\\n7. Czech Republic: $90.24\\n8. Portugal: $77.24\\n9. India: $75.26\\n10. Chile: $46.62\\n\\nThe country whose customers spent the most is the USA, with a total sales of $523.06.'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke(\n", + " \"List the total sales per country. Which country's customers spent the most?\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_list_tables` with `{}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_schema` with `PlaylistTrack`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m\n", + "CREATE TABLE \"PlaylistTrack\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from PlaylistTrack table:\n", + "PlaylistId\tTrackId\n", + "1\t3402\n", + "1\t3389\n", + "1\t3390\n", + "*/\u001b[0m\u001b[32;1m\u001b[1;3mThe `PlaylistTrack` table has two columns: `PlaylistId` and `TrackId`. It is a junction table that represents the many-to-many relationship between playlists and tracks. \n", + "\n", + "Here is the schema of the `PlaylistTrack` table:\n", + "\n", + "```\n", + "CREATE TABLE \"PlaylistTrack\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + ")\n", + "```\n", + "\n", + "The `PlaylistId` column is a foreign key referencing the `PlaylistId` column in the `Playlist` table. The `TrackId` column is a foreign key referencing the `TrackId` column in the `Track` table.\n", + "\n", + "Here are three sample rows from the `PlaylistTrack` table:\n", + "\n", + "```\n", + "PlaylistId TrackId\n", + "1 3402\n", + "1 3389\n", + "1 3390\n", + "```\n", + "\n", + "Please let me know if there is anything else I can help with.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Describe the playlisttrack table',\n", + " 'output': 'The `PlaylistTrack` table has two columns: `PlaylistId` and `TrackId`. It is a junction table that represents the many-to-many relationship between playlists and tracks. \\n\\nHere is the schema of the `PlaylistTrack` table:\\n\\n```\\nCREATE TABLE \"PlaylistTrack\" (\\n\\t\"PlaylistId\" INTEGER NOT NULL, \\n\\t\"TrackId\" INTEGER NOT NULL, \\n\\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \\n\\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \\n\\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\\n)\\n```\\n\\nThe `PlaylistId` column is a foreign key referencing the `PlaylistId` column in the `Playlist` table. The `TrackId` column is a foreign key referencing the `TrackId` column in the `Track` table.\\n\\nHere are three sample rows from the `PlaylistTrack` table:\\n\\n```\\nPlaylistId TrackId\\n1 3402\\n1 3389\\n1 3390\\n```\\n\\nPlease let me know if there is anything else I can help with.'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke(\"Describe the playlisttrack table\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using a dynamic few-shot prompt\n", + "\n", + "To optimize agent performance, we can provide a custom prompt with domain-specific knowledge. In this case we'll create a few shot prompt with an example selector, that will dynamically build the few shot prompt based on the user input.\n", + "\n", + "First we need some user input <> SQL query examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "examples = [\n", + " {\"input\": \"List all artists.\", \"query\": \"SELECT * FROM Artist;\"},\n", + " {\n", + " \"input\": \"Find all albums for the artist 'AC/DC'.\",\n", + " \"query\": \"SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');\",\n", + " },\n", + " {\n", + " \"input\": \"List all tracks in the 'Rock' genre.\",\n", + " \"query\": \"SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\",\n", + " },\n", + " {\n", + " \"input\": \"Find the total duration of all tracks.\",\n", + " \"query\": \"SELECT SUM(Milliseconds) FROM Track;\",\n", + " },\n", + " {\n", + " \"input\": \"List all customers from Canada.\",\n", + " \"query\": \"SELECT * FROM Customer WHERE Country = 'Canada';\",\n", + " },\n", + " {\n", + " \"input\": \"How many tracks are there in the album with ID 5?\",\n", + " \"query\": \"SELECT COUNT(*) FROM Track WHERE AlbumId = 5;\",\n", + " },\n", + " {\n", + " \"input\": \"Find the total number of invoices.\",\n", + " \"query\": \"SELECT COUNT(*) FROM Invoice;\",\n", + " },\n", + " {\n", + " \"input\": \"List all tracks that are longer than 5 minutes.\",\n", + " \"query\": \"SELECT * FROM Track WHERE Milliseconds > 300000;\",\n", + " },\n", + " {\n", + " \"input\": \"Who are the top 5 customers by total purchase?\",\n", + " \"query\": \"SELECT CustomerId, SUM(Total) AS TotalPurchase FROM Invoice GROUP BY CustomerId ORDER BY TotalPurchase DESC LIMIT 5;\",\n", + " },\n", + " {\n", + " \"input\": \"Which albums are from the year 2000?\",\n", + " \"query\": \"SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\",\n", + " },\n", + " {\n", + " \"input\": \"How many employees are there\",\n", + " \"query\": 'SELECT COUNT(*) FROM \"Employee\"',\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can create an example selector. This will take the actual user input and select some number of examples to add to our few-shot prompt. We'll use a SemanticSimilarityExampleSelector, which will perform a semantic search using the embeddings and vector store we configure to find the examples most similar to our input:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\n", + "from langchain_core.example_selectors import SemanticSimilarityExampleSelector\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "example_selector = SemanticSimilarityExampleSelector.from_examples(\n", + " examples,\n", + " OpenAIEmbeddings(),\n", + " FAISS,\n", + " k=5,\n", + " input_keys=[\"input\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can create our FewShotPromptTemplate, which takes our example selector, an example prompt for formatting each example, and a string prefix and suffix to put before and after our formatted examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import (\n", + " ChatPromptTemplate,\n", + " FewShotPromptTemplate,\n", + " MessagesPlaceholder,\n", + " PromptTemplate,\n", + " SystemMessagePromptTemplate,\n", + ")\n", + "\n", + "system_prefix = \"\"\"You are an agent designed to interact with a SQL database.\n", + "Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.\n", + "Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.\n", + "You can order the results by a relevant column to return the most interesting examples in the database.\n", + "Never query for all the columns from a specific table, only ask for the relevant columns given the question.\n", + "You have access to tools for interacting with the database.\n", + "Only use the given tools. Only use the information returned by the tools to construct your final answer.\n", + "You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.\n", + "\n", + "DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.\n", + "\n", + "If the question does not seem related to the database, just return \"I don't know\" as the answer.\n", + "\n", + "Here are some examples of user inputs and their corresponding SQL queries:\"\"\"\n", + "\n", + "few_shot_prompt = FewShotPromptTemplate(\n", + " example_selector=example_selector,\n", + " example_prompt=PromptTemplate.from_template(\n", + " \"User input: {input}\\nSQL query: {query}\"\n", + " ),\n", + " input_variables=[\"input\", \"dialect\", \"top_k\"],\n", + " prefix=system_prefix,\n", + " suffix=\"\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since our underlying agent is an [OpenAI tools agent](/docs/modules/agents/agent_types/openai_tools), which uses OpenAI function calling, our full prompt should be a chat prompt with a human message template and an agent_scratchpad `MessagesPlaceholder`. The few-shot prompt will be used for our system message:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "full_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " SystemMessagePromptTemplate(prompt=few_shot_prompt),\n", + " (\"human\", \"{input}\"),\n", + " MessagesPlaceholder(\"agent_scratchpad\"),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System: You are an agent designed to interact with a SQL database.\n", + "Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer.\n", + "Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results.\n", + "You can order the results by a relevant column to return the most interesting examples in the database.\n", + "Never query for all the columns from a specific table, only ask for the relevant columns given the question.\n", + "You have access to tools for interacting with the database.\n", + "Only use the given tools. Only use the information returned by the tools to construct your final answer.\n", + "You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.\n", + "\n", + "DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.\n", + "\n", + "If the question does not seem related to the database, just return \"I don't know\" as the answer.\n", + "\n", + "Here are some examples of user inputs and their corresponding SQL queries:\n", + "\n", + "User input: List all artists.\n", + "SQL query: SELECT * FROM Artist;\n", + "\n", + "User input: How many employees are there\n", + "SQL query: SELECT COUNT(*) FROM \"Employee\"\n", + "\n", + "User input: How many tracks are there in the album with ID 5?\n", + "SQL query: SELECT COUNT(*) FROM Track WHERE AlbumId = 5;\n", + "\n", + "User input: List all tracks in the 'Rock' genre.\n", + "SQL query: SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\n", + "\n", + "User input: Which albums are from the year 2000?\n", + "SQL query: SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\n", + "Human: How many arists are there\n" + ] + } + ], + "source": [ + "# Example formatted prompt\n", + "prompt_val = full_prompt.invoke(\n", + " {\n", + " \"input\": \"How many arists are there\",\n", + " \"top_k\": 5,\n", + " \"dialect\": \"SQLite\",\n", + " \"agent_scratchpad\": [],\n", + " }\n", + ")\n", + "print(prompt_val.to_string())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we can create our agent with our custom prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "agent = create_sql_agent(\n", + " llm=llm,\n", + " db=db,\n", + " prompt=full_prompt,\n", + " verbose=True,\n", + " agent_type=\"openai-tools\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try it out:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_query` with `{'query': 'SELECT COUNT(*) FROM Artist'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[(275,)]\u001b[0m\u001b[32;1m\u001b[1;3mThere are 275 artists in the database.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'How many artists are there?',\n", + " 'output': 'There are 275 artists in the database.'}" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent.invoke({\"input\": \"How many artists are there?\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dealing with high-cardinality columns\n", + "\n", + "In order to filter columns that contain proper nouns such as addresses, song names or artists, we first need to double-check the spelling in order to filter the data correctly. \n", + "\n", + "We can achieve this by creating a vector store with all the distinct proper nouns that exist in the database. We can then have the agent query that vector store each time the user includes a proper noun in their question, to find the correct spelling for that word. In this way, the agent can make sure it understands which entity the user is referring to before building the target query.\n", + "\n", + "First we need the unique values for each entity we want, for which we define a function that parses the result into a list of elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['For Those About To Rock We Salute You',\n", + " 'Balls to the Wall',\n", + " 'Restless and Wild',\n", + " 'Let There Be Rock',\n", + " 'Big Ones']" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import ast\n", + "import re\n", + "\n", + "\n", + "def query_as_list(db, query):\n", + " res = db.run(query)\n", + " res = [el for sub in ast.literal_eval(res) for el in sub if el]\n", + " res = [re.sub(r\"\\b\\d+\\b\", \"\", string).strip() for string in res]\n", + " return res\n", + "\n", + "\n", + "artists = query_as_list(db, \"SELECT Name FROM Artist\")\n", + "albums = query_as_list(db, \"SELECT Title FROM Album\")\n", + "albums[:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can proceed with creating the custom **retriever tool** and the final agent:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents.agent_toolkits import create_retriever_tool\n", + "\n", + "vector_db = FAISS.from_texts(artists + albums, OpenAIEmbeddings())\n", + "retriever = vector_db.as_retriever(search_kwargs={\"k\": 5})\n", + "description = \"\"\"Use to look up values to filter on. Input is an approximate spelling of the proper noun, output is \\\n", + "valid proper nouns. Use the noun most similar to the search.\"\"\"\n", + "retriever_tool = create_retriever_tool(\n", + " retriever,\n", + " name=\"search_proper_nouns\",\n", + " description=description,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "system = \"\"\"You are an agent designed to interact with a SQL database.\n", + "Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.\n", + "Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.\n", + "You can order the results by a relevant column to return the most interesting examples in the database.\n", + "Never query for all the columns from a specific table, only ask for the relevant columns given the question.\n", + "You have access to tools for interacting with the database.\n", + "Only use the given tools. Only use the information returned by the tools to construct your final answer.\n", + "You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.\n", + "\n", + "DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.\n", + "\n", + "If you need to filter on a proper noun, you must ALWAYS first look up the filter value using the \"search_proper_nouns\" tool!\n", + "\n", + "You have access to the following tables: {table_names}\n", + "\n", + "If the question does not seem related to the database, just return \"I don't know\" as the answer.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", \"{input}\"), MessagesPlaceholder(\"agent_scratchpad\")])\n", + "agent = create_sql_agent(\n", + " llm=llm,\n", + " db=db,\n", + " extra_tools=[retriever_tool],\n", + " prompt=prompt,\n", + " agent_type=\"openai-tools\",\n", + " verbose=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `search_proper_nouns` with `{'query': 'alice in chains'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3mAlice In Chains\n", + "\n", + "Metallica\n", + "\n", + "Pearl Jam\n", + "\n", + "Pearl Jam\n", + "\n", + "Smashing Pumpkins\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_query` with `{'query': \"SELECT COUNT(*) FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'Alice In Chains')\"}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[(1,)]\u001b[0m\u001b[32;1m\u001b[1;3mAlice In Chains has 1 album.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'How many albums does alice in chains have?',\n", + " 'output': 'Alice In Chains has 1 album.'}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent.invoke({\"input\": \"How many albums does alice in chains have?\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the agent used the `search_proper_nouns` tool in order to check how to correctly query the database for this specific artist." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Under the hood, `create_sql_agent` is just passing in SQL tools to more generic agent constructors. To learn more about the built-in generic agent types as well as how to build custom agents, head to the [Agents Modules](/docs/modules/agents/).\n", + "\n", + "The built-in `AgentExecutor` runs a simple Agent action -> Tool call -> Agent action... loop. To build more complex agent runtimes, head to the [LangGraph section](/docs/langgraph)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/docs/use_cases/sql/index.ipynb b/docs/docs/use_cases/sql/index.ipynb new file mode 100644 index 0000000000000..1b706c831b168 --- /dev/null +++ b/docs/docs/use_cases/sql/index.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0.5\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SQL\n", + "\n", + "One of the most common types of databases that we can build Q&A systems for are SQL databases. LangChain comes with a number of built-in chains and agents that are compatible with any SQL dialect supported by SQLAlchemy (e.g., MySQL, PostgreSQL, Oracle SQL, Databricks, SQLite). They enable use cases such as:\n", + "\n", + "* Generating queries that will be run based on natural language questions,\n", + "* Creating chatbots that can answer questions based on database data,\n", + "* Building custom dashboards based on insights a user wants to analyze,\n", + "\n", + "and much more.\n", + "\n", + "## ⚠️ Security note ⚠️\n", + "\n", + "Building Q&A systems of SQL databases requires executing model-generated SQL queries. There are inherent risks in doing this. Make sure that your database connection permissions are always scoped as narrowly as possible for your chain/agent's needs. This will mitigate though not eliminate the risks of building a model-driven system. For more on general security best practices, [see here](/docs/security).\n", + "\n", + "![sql_usecase.png](../../../static/img/sql_usecase.png)\n", + "\n", + "## Quickstart\n", + "\n", + "Head to the **[Quickstart](/docs/use_cases/sql/quickstart)** page to get started.\n", + "\n", + "## Advanced\n", + "\n", + "Once you've familiarized yourself with the basics, you can head to the advanced guides:\n", + "\n", + "* [Agents](/docs/use_cases/sql/agents): Building agents that can interact with SQL DBs.\n", + "* [Prompting strategies](/docs/use_cases/sql/prompting): Strategies for improving SQL query generation.\n", + "* [Query validation](/docs/use_cases/sql/query_checking): How to validate SQL queries.\n", + "* [Large databases](/docs/use_cases/sql/large_db): How to interact with DBs with many tables and high-cardinality columns." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/docs/use_cases/sql/large_db.ipynb b/docs/docs/use_cases/sql/large_db.ipynb new file mode 100644 index 0000000000000..dd034037d1a4b --- /dev/null +++ b/docs/docs/use_cases/sql/large_db.ipynb @@ -0,0 +1,627 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "b2788654-3f62-4e2a-ab00-471922cc54df", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 4\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "6751831d-9b08-434f-829b-d0052a3b119f", + "metadata": {}, + "source": [ + "# Large databases\n", + "\n", + "In order to write valid queries against a database, we need to feed the model the table names, table schemas, and feature values for it to query over. When there are many tables, columns, and/or high-cardinality columns, it becomes impossible for us to dump the full information about our database in every prompt. Instead, we must find ways to dynamically insert into the prompt only the most relevant information. Let's take a look at some techniques for doing this.\n", + "\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9675e433-e608-469e-b04e-2847479a8310", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "4f56ff5d-b2e4-49e3-a0b4-fb99466cfedc", + "metadata": {}, + "source": [ + "We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "06d8dd03-2d7b-4fef-b145-43c074eacb8b", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + " ········\n" + ] + } + ], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Uncomment the below to use LangSmith. Not required.\n", + "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "id": "590ee096-db88-42af-90d4-99b8149df753", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven [SQLDatabase](https://api.python.langchain.com/en/latest/utilities/langchain_community.utilities.sql_database.SQLDatabase.html) class:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cebd3915-f58f-4e73-8459-265630ae8cd4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\"" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM Artist LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "id": "2e572e1f-99b5-46a2-9023-76d1e6256c0a", + "metadata": {}, + "source": [ + "## Many tables\n", + "\n", + "One of the main pieces of information we need to include in our prompt is the schemas of the relevant tables. When we have very many tables, we can't fit all of the schemas in a single prompt. What we can do in such cases is first extract the names of the tables related to the user input, and then include only their schemas.\n", + "\n", + "One easy and reliable way to do this is using OpenAI function-calling and Pydantic models. LangChain comes with a built-in [create_extraction_chain_pydantic](https://api.python.langchain.com/en/latest/chains/langchain.chains.openai_tools.extraction.create_extraction_chain_pydantic.html) chain that lets us do just this:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d8236886-c54f-4bdb-ad74-2514888628fd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Table(name='Genre'), Table(name='Artist'), Table(name='Track')]" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chains.openai_tools import create_extraction_chain_pydantic\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", temperature=0)\n", + "\n", + "\n", + "class Table(BaseModel):\n", + " \"\"\"Table in SQL database.\"\"\"\n", + "\n", + " name: str = Field(description=\"Name of table in SQL database.\")\n", + "\n", + "\n", + "table_names = \"\\n\".join(db.get_usable_table_names())\n", + "system = f\"\"\"Return the names of ALL the SQL tables that MIGHT be relevant to the user question. \\\n", + "The tables are:\n", + "\n", + "{table_names}\n", + "\n", + "Remember to include ALL POTENTIALLY RELEVANT tables, even if you're not sure that they're needed.\"\"\"\n", + "table_chain = create_extraction_chain_pydantic(Table, llm, system_message=system)\n", + "table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "markdown", + "id": "1641dbba-d359-4cb2-ac52-82dfae99f392", + "metadata": {}, + "source": [ + "This works pretty well! Except, as we'll see below, we actually need a few other tables as well. This would be pretty difficult for the model to know based just on the user question. In this case, we might think to simplify our model's job by grouping the tables together. We'll just ask the model to choose between categories \"Music\" and \"Business\", and then take care of selecting all the relevant tables from there:" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "0ccb0bf5-c580-428f-9cde-a58772ae784e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Table(name='Music')]" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = f\"\"\"Return the names of the SQL tables that are relevant to the user question. \\\n", + "The tables are:\n", + "\n", + "Music\n", + "Business\"\"\"\n", + "category_chain = create_extraction_chain_pydantic(Table, llm, system_message=system)\n", + "category_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "ae4899fc-6f8a-4b10-983c-9e3fef4a7bb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Album', 'Artist', 'Genre', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from typing import List\n", + "\n", + "\n", + "def get_tables(categories: List[Table]) -> List[str]:\n", + " tables = []\n", + " for category in categories:\n", + " if category.name == \"Music\":\n", + " tables.extend(\n", + " [\n", + " \"Album\",\n", + " \"Artist\",\n", + " \"Genre\",\n", + " \"MediaType\",\n", + " \"Playlist\",\n", + " \"PlaylistTrack\",\n", + " \"Track\",\n", + " ]\n", + " )\n", + " elif category.name == \"Business\":\n", + " tables.extend([\"Customer\", \"Employee\", \"Invoice\", \"InvoiceLine\"])\n", + " return tables\n", + "\n", + "\n", + "table_chain = category_chain | get_tables # noqa\n", + "table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "markdown", + "id": "04d52d01-1ccf-4753-b34a-0dcbc4921f78", + "metadata": {}, + "source": [ + "Now that we've got a chain that can output the relevant tables for any query we can combine this with our [create_sql_query_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.sql_database.query.create_sql_query_chain.html), which can accept a list of `table_names_to_use` to determine which table schemas are included in the prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "79f2a5a2-eb99-47e3-9c2b-e5751a800174", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain.chains import create_sql_query_chain\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "query_chain = create_sql_query_chain(llm, db)\n", + "# Convert \"question\" key to the \"input\" key expected by current table_chain.\n", + "table_chain = {\"input\": itemgetter(\"question\")} | table_chain\n", + "# Set table_names_to_use using table_chain.\n", + "full_chain = RunnablePassthrough.assign(table_names_to_use=table_chain) | query_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "424a7564-f63c-4584-b734-88021926486d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT \"Genre\".\"Name\"\n", + "FROM \"Genre\"\n", + "JOIN \"Track\" ON \"Genre\".\"GenreId\" = \"Track\".\"GenreId\"\n", + "JOIN \"Album\" ON \"Track\".\"AlbumId\" = \"Album\".\"AlbumId\"\n", + "JOIN \"Artist\" ON \"Album\".\"ArtistId\" = \"Artist\".\"ArtistId\"\n", + "WHERE \"Artist\".\"Name\" = 'Alanis Morissette'\n" + ] + } + ], + "source": [ + "query = full_chain.invoke(\n", + " {\"question\": \"What are all the genres of Alanis Morisette songs\"}\n", + ")\n", + "print(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "3fb715cf-69d1-46a6-a1a7-9715ee550a0c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"[('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',), ('Rock',)]\"" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "bb3d12b0-81a6-4250-8bc4-d58fe762c4cc", + "metadata": {}, + "source": [ + "We might rephrase our question slightly to remove redundancy in the answer" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "010b5c3c-d55b-461a-8de5-8f1a8b2c56ec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT g.Name\n", + "FROM Genre g\n", + "JOIN Track t ON g.GenreId = t.GenreId\n", + "JOIN Album a ON t.AlbumId = a.AlbumId\n", + "JOIN Artist ar ON a.ArtistId = ar.ArtistId\n", + "WHERE ar.Name = 'Alanis Morissette'\n" + ] + } + ], + "source": [ + "query = full_chain.invoke(\n", + " {\"question\": \"What is the set of all unique genres of Alanis Morisette songs\"}\n", + ")\n", + "print(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "d21c0563-1f55-4577-8222-b0e9802f1c4b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"[('Rock',)]\"" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "7a717020-84c2-40f3-ba84-6624138d8e0c", + "metadata": {}, + "source": [ + "We can see the [LangSmith trace](https://smith.langchain.com/public/20b8ef90-1dac-4754-90f0-6bc11203c50a/r) for this run here.\n", + "\n", + "We've seen how to dynamically include a subset of table schemas in a prompt within a chain. Another possible approach to this problem is to let an Agent decide for itself when to look up tables by giving it a Tool to do so. You can see an example of this in the [SQL: Agents](/docs/use_cases/sql/agents) guide." + ] + }, + { + "cell_type": "markdown", + "id": "cb9e54fd-64ca-4ed5-847c-afc635aae4f5", + "metadata": {}, + "source": [ + "## High-cardinality columns\n", + "\n", + "In order to filter columns that contain proper nouns such as addresses, song names or artists, we first need to double-check the spelling in order to filter the data correctly. \n", + "\n", + "One naive strategy it to create a vector store with all the distinct proper nouns that exist in the database. We can then query that vector store each user input and inject the most relevant proper nouns into the prompt.\n", + "\n", + "First we need the unique values for each entity we want, for which we define a function that parses the result into a list of elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "dee1b9e1-36b0-4cc1-ab78-7a872ad87e29", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['AC/DC', 'Accept', 'Aerosmith', 'Alanis Morissette', 'Alice In Chains']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import ast\n", + "import re\n", + "\n", + "\n", + "def query_as_list(db, query):\n", + " res = db.run(query)\n", + " res = [el for sub in ast.literal_eval(res) for el in sub if el]\n", + " res = [re.sub(r\"\\b\\d+\\b\", \"\", string).strip() for string in res]\n", + " return res\n", + "\n", + "\n", + "proper_nouns = query_as_list(db, \"SELECT Name FROM Artist\")\n", + "proper_nouns += query_as_list(db, \"SELECT Title FROM Album\")\n", + "proper_nouns += query_as_list(db, \"SELECT Name FROM Genre\")\n", + "len(proper_nouns)\n", + "proper_nouns[:5]" + ] + }, + { + "cell_type": "markdown", + "id": "22efa968-1879-4d7a-858f-7899dfa57454", + "metadata": {}, + "source": [ + "Now we can embed and store all of our values in a vector database:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ea50abce-545a-4dc3-8795-8d364f7d142a", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "vector_db = FAISS.from_texts(proper_nouns, OpenAIEmbeddings())\n", + "retriever = vector_db.as_retriever(search_kwargs={\"k\": 15})" + ] + }, + { + "cell_type": "markdown", + "id": "a5d1d5c0-0928-40a4-b961-f1afe03cd5d3", + "metadata": {}, + "source": [ + "And put together a query construction chain that first retrieves values from the database and inserts them into the prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "aea123ae-d809-44a0-be5d-d883c60d6a11", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "system = \"\"\"You are a SQLite expert. Given an input question, create a syntactically \\\n", + "correct SQLite query to run. Unless otherwise specificed, do not return more than \\\n", + "{top_k} rows.\\n\\nHere is the relevant table info: {table_info}\\n\\nHere is a non-exhaustive \\\n", + "list of possible feature values. If filtering on a feature value make sure to check its spelling \\\n", + "against this list first:\\n\\n{proper_nouns}\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", \"{input}\")])\n", + "\n", + "query_chain = create_sql_query_chain(llm, db, prompt=prompt)\n", + "retriever_chain = (\n", + " itemgetter(\"question\")\n", + " | retriever\n", + " | (lambda docs: \"\\n\".join(doc.page_content for doc in docs))\n", + ")\n", + "chain = RunnablePassthrough.assign(proper_nouns=retriever_chain) | query_chain" + ] + }, + { + "cell_type": "markdown", + "id": "12b0ed60-2536-4f82-85df-e096a272072a", + "metadata": {}, + "source": [ + "To try out our chain, let's see what happens when we try filtering on \"elenis moriset\", a mispelling of Alanis Morissette, without and with retrieval:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "fcdd8432-07a4-4609-8214-b1591dd94950", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT Genre.Name\n", + "FROM Genre\n", + "JOIN Track ON Genre.GenreId = Track.GenreId\n", + "JOIN Album ON Track.AlbumId = Album.AlbumId\n", + "JOIN Artist ON Album.ArtistId = Artist.ArtistId\n", + "WHERE Artist.Name = 'Elenis Moriset'\n" + ] + }, + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Without retrieval\n", + "query = query_chain.invoke(\n", + " {\"question\": \"What are all the genres of elenis moriset songs\", \"proper_nouns\": \"\"}\n", + ")\n", + "print(query)\n", + "db.run(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "e8a3231a-8590-46f5-a954-da06829ee6df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT Genre.Name\n", + "FROM Genre\n", + "JOIN Track ON Genre.GenreId = Track.GenreId\n", + "JOIN Album ON Track.AlbumId = Album.AlbumId\n", + "JOIN Artist ON Album.ArtistId = Artist.ArtistId\n", + "WHERE Artist.Name = 'Alanis Morissette'\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[('Rock',)]\"" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# With retrieval\n", + "query = chain.invoke({\"question\": \"What are all the genres of elenis moriset songs\"})\n", + "print(query)\n", + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "7f99181b-a75c-4ff3-b37b-33f99a506581", + "metadata": {}, + "source": [ + "We can see that with retrieval we're able to correct the spelling and get back a valid result.\n", + "\n", + "Another possible approach to this problem is to let an Agent decide for itself when to look up proper nouns. You can see an example of this in the [SQL: Agents](/docs/use_cases/sql/agents) guide." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/sql/prompting.ipynb b/docs/docs/use_cases/sql/prompting.ipynb new file mode 100644 index 0000000000000..27a60b3de9d7e --- /dev/null +++ b/docs/docs/use_cases/sql/prompting.ipynb @@ -0,0 +1,789 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 2\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prompting strategies\n", + "\n", + "In this guide we'll go over prompting strategies to improve SQL query generation. We'll largely focus on methods for getting relevant database-specific information in your prompt.\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-experimental langchain-openai" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Uncomment the below to use LangSmith. Not required.\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven `SQLDatabase` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\"" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\", sample_rows_in_table_info=3)\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM Artist LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dialect-specific prompting\n", + "\n", + "One of the simplest things we can do is make our prompt specific to the SQL dialect we're using. When using the built-in [create_sql_query_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.sql_database.query.create_sql_query_chain.html) and [SQLDatabase](https://api.python.langchain.com/en/latest/utilities/langchain_community.utilities.sql_database.SQLDatabase.html), this is handled for you for any of the following dialects:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['crate',\n", + " 'duckdb',\n", + " 'googlesql',\n", + " 'mssql',\n", + " 'mysql',\n", + " 'mariadb',\n", + " 'oracle',\n", + " 'postgresql',\n", + " 'sqlite',\n", + " 'clickhouse',\n", + " 'prestodb']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chains.sql_database.prompt import SQL_PROMPTS\n", + "\n", + "list(SQL_PROMPTS)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, using our current DB we can see that we'll get a SQLite-specific prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Use the following format:\n", + "\n", + "Question: Question here\n", + "SQLQuery: SQL Query to run\n", + "SQLResult: Result of the SQLQuery\n", + "Answer: Final answer here\n", + "\n", + "Only use the following tables:\n", + "\u001b[33;1m\u001b[1;3m{table_info}\u001b[0m\n", + "\n", + "Question: \u001b[33;1m\u001b[1;3m{input}\u001b[0m\n" + ] + } + ], + "source": [ + "from langchain.chains import create_sql_query_chain\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=\"0\")\n", + "chain = create_sql_query_chain(llm, db)\n", + "chain.get_prompts()[0].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table definitions and example rows\n", + "\n", + "In basically any SQL chain, we'll need to feed the model at least part of the database schema. Without this it won't be able to write valid queries. Our database comes with some convenience methods to give us the relevant context. Specifically, we can get the table names, their schemas, and a sample of rows from each table:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['table_info', 'table_names']\n", + "\n", + "CREATE TABLE \"Album\" (\n", + "\t\"AlbumId\" INTEGER NOT NULL, \n", + "\t\"Title\" NVARCHAR(160) NOT NULL, \n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"AlbumId\"), \n", + "\tFOREIGN KEY(\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Album table:\n", + "AlbumId\tTitle\tArtistId\n", + "1\tFor Those About To Rock We Salute You\t1\n", + "2\tBalls to the Wall\t2\n", + "3\tRestless and Wild\t2\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Artist\" (\n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"ArtistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Artist table:\n", + "ArtistId\tName\n", + "1\tAC/DC\n", + "2\tAccept\n", + "3\tAerosmith\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Customer\" (\n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"FirstName\" NVARCHAR(40) NOT NULL, \n", + "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", + "\t\"Company\" NVARCHAR(80), \n", + "\t\"Address\" NVARCHAR(70), \n", + "\t\"City\" NVARCHAR(40), \n", + "\t\"State\" NVARCHAR(40), \n", + "\t\"Country\" NVARCHAR(40), \n", + "\t\"PostalCode\" NVARCHAR(10), \n", + "\t\"Phone\" NVARCHAR(24), \n", + "\t\"Fax\" NVARCHAR(24), \n", + "\t\"Email\" NVARCHAR(60) NOT NULL, \n", + "\t\"SupportRepId\" INTEGER, \n", + "\tPRIMARY KEY (\"CustomerId\"), \n", + "\tFOREIGN KEY(\"SupportRepId\") REFERENCES \"Employee\" (\"EmployeeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Customer table:\n", + "CustomerId\tFirstName\tLastName\tCompany\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\tSupportRepId\n", + "1\tLuís\tGonçalves\tEmbraer - Empresa Brasileira de Aeronáutica S.A.\tAv. Brigadeiro Faria Lima, 2170\tSão José dos Campos\tSP\tBrazil\t12227-000\t+55 (12) 3923-5555\t+55 (12) 3923-5566\tluisg@embraer.com.br\t3\n", + "2\tLeonie\tKöhler\tNone\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t+49 0711 2842222\tNone\tleonekohler@surfeu.de\t5\n", + "3\tFrançois\tTremblay\tNone\t1498 rue Bélanger\tMontréal\tQC\tCanada\tH2G 1A7\t+1 (514) 721-4711\tNone\tftremblay@gmail.com\t3\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Employee\" (\n", + "\t\"EmployeeId\" INTEGER NOT NULL, \n", + "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", + "\t\"FirstName\" NVARCHAR(20) NOT NULL, \n", + "\t\"Title\" NVARCHAR(30), \n", + "\t\"ReportsTo\" INTEGER, \n", + "\t\"BirthDate\" DATETIME, \n", + "\t\"HireDate\" DATETIME, \n", + "\t\"Address\" NVARCHAR(70), \n", + "\t\"City\" NVARCHAR(40), \n", + "\t\"State\" NVARCHAR(40), \n", + "\t\"Country\" NVARCHAR(40), \n", + "\t\"PostalCode\" NVARCHAR(10), \n", + "\t\"Phone\" NVARCHAR(24), \n", + "\t\"Fax\" NVARCHAR(24), \n", + "\t\"Email\" NVARCHAR(60), \n", + "\tPRIMARY KEY (\"EmployeeId\"), \n", + "\tFOREIGN KEY(\"ReportsTo\") REFERENCES \"Employee\" (\"EmployeeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Employee table:\n", + "EmployeeId\tLastName\tFirstName\tTitle\tReportsTo\tBirthDate\tHireDate\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\n", + "1\tAdams\tAndrew\tGeneral Manager\tNone\t1962-02-18 00:00:00\t2002-08-14 00:00:00\t11120 Jasper Ave NW\tEdmonton\tAB\tCanada\tT5K 2N1\t+1 (780) 428-9482\t+1 (780) 428-3457\tandrew@chinookcorp.com\n", + "2\tEdwards\tNancy\tSales Manager\t1\t1958-12-08 00:00:00\t2002-05-01 00:00:00\t825 8 Ave SW\tCalgary\tAB\tCanada\tT2P 2T3\t+1 (403) 262-3443\t+1 (403) 262-3322\tnancy@chinookcorp.com\n", + "3\tPeacock\tJane\tSales Support Agent\t2\t1973-08-29 00:00:00\t2002-04-01 00:00:00\t1111 6 Ave SW\tCalgary\tAB\tCanada\tT2P 5M5\t+1 (403) 262-3443\t+1 (403) 262-6712\tjane@chinookcorp.com\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Genre\" (\n", + "\t\"GenreId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"GenreId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Genre table:\n", + "GenreId\tName\n", + "1\tRock\n", + "2\tJazz\n", + "3\tMetal\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Invoice\" (\n", + "\t\"InvoiceId\" INTEGER NOT NULL, \n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"InvoiceDate\" DATETIME NOT NULL, \n", + "\t\"BillingAddress\" NVARCHAR(70), \n", + "\t\"BillingCity\" NVARCHAR(40), \n", + "\t\"BillingState\" NVARCHAR(40), \n", + "\t\"BillingCountry\" NVARCHAR(40), \n", + "\t\"BillingPostalCode\" NVARCHAR(10), \n", + "\t\"Total\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"InvoiceId\"), \n", + "\tFOREIGN KEY(\"CustomerId\") REFERENCES \"Customer\" (\"CustomerId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Invoice table:\n", + "InvoiceId\tCustomerId\tInvoiceDate\tBillingAddress\tBillingCity\tBillingState\tBillingCountry\tBillingPostalCode\tTotal\n", + "1\t2\t2009-01-01 00:00:00\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t1.98\n", + "2\t4\t2009-01-02 00:00:00\tUllevålsveien 14\tOslo\tNone\tNorway\t0171\t3.96\n", + "3\t8\t2009-01-03 00:00:00\tGrétrystraat 63\tBrussels\tNone\tBelgium\t1000\t5.94\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"InvoiceLine\" (\n", + "\t\"InvoiceLineId\" INTEGER NOT NULL, \n", + "\t\"InvoiceId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", + "\t\"Quantity\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"InvoiceLineId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"InvoiceId\") REFERENCES \"Invoice\" (\"InvoiceId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from InvoiceLine table:\n", + "InvoiceLineId\tInvoiceId\tTrackId\tUnitPrice\tQuantity\n", + "1\t1\t2\t0.99\t1\n", + "2\t1\t4\t0.99\t1\n", + "3\t2\t6\t0.99\t1\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"MediaType\" (\n", + "\t\"MediaTypeId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"MediaTypeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from MediaType table:\n", + "MediaTypeId\tName\n", + "1\tMPEG audio file\n", + "2\tProtected AAC audio file\n", + "3\tProtected MPEG-4 video file\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Playlist\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"PlaylistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Playlist table:\n", + "PlaylistId\tName\n", + "1\tMusic\n", + "2\tMovies\n", + "3\tTV Shows\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"PlaylistTrack\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from PlaylistTrack table:\n", + "PlaylistId\tTrackId\n", + "1\t3402\n", + "1\t3389\n", + "1\t3390\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Track\" (\n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(200) NOT NULL, \n", + "\t\"AlbumId\" INTEGER, \n", + "\t\"MediaTypeId\" INTEGER NOT NULL, \n", + "\t\"GenreId\" INTEGER, \n", + "\t\"Composer\" NVARCHAR(220), \n", + "\t\"Milliseconds\" INTEGER NOT NULL, \n", + "\t\"Bytes\" INTEGER, \n", + "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"TrackId\"), \n", + "\tFOREIGN KEY(\"MediaTypeId\") REFERENCES \"MediaType\" (\"MediaTypeId\"), \n", + "\tFOREIGN KEY(\"GenreId\") REFERENCES \"Genre\" (\"GenreId\"), \n", + "\tFOREIGN KEY(\"AlbumId\") REFERENCES \"Album\" (\"AlbumId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Track table:\n", + "TrackId\tName\tAlbumId\tMediaTypeId\tGenreId\tComposer\tMilliseconds\tBytes\tUnitPrice\n", + "1\tFor Those About To Rock (We Salute You)\t1\t1\t1\tAngus Young, Malcolm Young, Brian Johnson\t343719\t11170334\t0.99\n", + "2\tBalls to the Wall\t2\t2\t1\tNone\t342562\t5510424\t0.99\n", + "3\tFast As a Shark\t3\t2\t1\tF. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman\t230619\t3990994\t0.99\n", + "*/\n" + ] + } + ], + "source": [ + "context = db.get_context()\n", + "print(list(context))\n", + "print(context[\"table_info\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we don't have too many, or too wide of, tables, we can just insert the entirety of this information in our prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Use the following format:\n", + "\n", + "Question: Question here\n", + "SQLQuery: SQL Query to run\n", + "SQLResult: Result of the SQLQuery\n", + "Answer: Final answer here\n", + "\n", + "Only use the following tables:\n", + "\n", + "CREATE TABLE \"Album\" (\n", + "\t\"AlbumId\" INTEGER NOT NULL, \n", + "\t\"Title\" NVARCHAR(160) NOT NULL, \n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"AlbumId\"), \n", + "\tFOREIGN KEY(\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Album table:\n", + "AlbumId\tTitle\tArtistId\n", + "1\tFor Those About To Rock We Salute You\t1\n", + "2\tBalls to the Wall\t2\n", + "3\tRestless and Wild\t2\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Artist\" (\n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120)\n" + ] + } + ], + "source": [ + "prompt_with_context = chain.get_prompts()[0].partial(table_info=context[\"table_info\"])\n", + "print(prompt_with_context.pretty_repr()[:1500])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we do have database schemas that are too large to fit into our model's context window, we'll need to come up with ways of inserting only the relevant table definitions into the prompt based on the user input. For more on this head to the [Many tables, wide tables, high-cardinality feature](/docs/use_cases/sql/large_db) guide." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Few-shot examples\n", + "\n", + "Including examples of natural language questions being converted to valid SQL queries against our database in the prompt will often improve model performance, especially for complex queries.\n", + "\n", + "Let's say we have the following examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "examples = [\n", + " {\"input\": \"List all artists.\", \"query\": \"SELECT * FROM Artist;\"},\n", + " {\n", + " \"input\": \"Find all albums for the artist 'AC/DC'.\",\n", + " \"query\": \"SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');\",\n", + " },\n", + " {\n", + " \"input\": \"List all tracks in the 'Rock' genre.\",\n", + " \"query\": \"SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\",\n", + " },\n", + " {\n", + " \"input\": \"Find the total duration of all tracks.\",\n", + " \"query\": \"SELECT SUM(Milliseconds) FROM Track;\",\n", + " },\n", + " {\n", + " \"input\": \"List all customers from Canada.\",\n", + " \"query\": \"SELECT * FROM Customer WHERE Country = 'Canada';\",\n", + " },\n", + " {\n", + " \"input\": \"How many tracks are there in the album with ID 5?\",\n", + " \"query\": \"SELECT COUNT(*) FROM Track WHERE AlbumId = 5;\",\n", + " },\n", + " {\n", + " \"input\": \"Find the total number of invoices.\",\n", + " \"query\": \"SELECT COUNT(*) FROM Invoice;\",\n", + " },\n", + " {\n", + " \"input\": \"List all tracks that are longer than 5 minutes.\",\n", + " \"query\": \"SELECT * FROM Track WHERE Milliseconds > 300000;\",\n", + " },\n", + " {\n", + " \"input\": \"Who are the top 5 customers by total purchase?\",\n", + " \"query\": \"SELECT CustomerId, SUM(Total) AS TotalPurchase FROM Invoice GROUP BY CustomerId ORDER BY TotalPurchase DESC LIMIT 5;\",\n", + " },\n", + " {\n", + " \"input\": \"Which albums are from the year 2000?\",\n", + " \"query\": \"SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\",\n", + " },\n", + " {\n", + " \"input\": \"How many employees are there\",\n", + " \"query\": 'SELECT COUNT(*) FROM \"Employee\"',\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can create a few-shot prompt with them like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate\n", + "\n", + "example_prompt = PromptTemplate.from_template(\"User input: {input}\\nSQL query: {query}\")\n", + "prompt = FewShotPromptTemplate(\n", + " examples=examples[:5],\n", + " example_prompt=example_prompt,\n", + " prefix=\"You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than {top_k} rows.\\n\\nHere is the relevant table info: {table_info}\\n\\nBelow are a number of examples of questions and their corresponding SQL queries.\",\n", + " suffix=\"User input: {input}\\nSQL query: \",\n", + " input_variables=[\"input\", \"top_k\", \"table_info\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than 3 rows.\n", + "\n", + "Here is the relevant table info: foo\n", + "\n", + "Below are a number of examples of questions and their corresponding SQL queries.\n", + "\n", + "User input: List all artists.\n", + "SQL query: SELECT * FROM Artist;\n", + "\n", + "User input: Find all albums for the artist 'AC/DC'.\n", + "SQL query: SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');\n", + "\n", + "User input: List all tracks in the 'Rock' genre.\n", + "SQL query: SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\n", + "\n", + "User input: Find the total duration of all tracks.\n", + "SQL query: SELECT SUM(Milliseconds) FROM Track;\n", + "\n", + "User input: List all customers from Canada.\n", + "SQL query: SELECT * FROM Customer WHERE Country = 'Canada';\n", + "\n", + "User input: How many artists are there?\n", + "SQL query: \n" + ] + } + ], + "source": [ + "print(prompt.format(input=\"How many artists are there?\", top_k=3, table_info=\"foo\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dynamic few-shot examples\n", + "\n", + "If we have enough examples, we may want to only include the most relevant ones in the prompt, either because they don't fit in the model's context window or because the long tail of examples distracts the model. And specifically, given any input we want to include the examples most relevant to that input.\n", + "\n", + "We can do just this using an ExampleSelector. In this case we'll use a [SemanticSimilarityExampleSelector](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector.html), which will store the examples in the vector database of our choosing. At runtime it will perform a similarity search between the input and our examples, and return the most semantically similar ones: " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\n", + "from langchain_core.example_selectors import SemanticSimilarityExampleSelector\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "example_selector = SemanticSimilarityExampleSelector.from_examples(\n", + " examples,\n", + " OpenAIEmbeddings(),\n", + " FAISS,\n", + " k=5,\n", + " input_keys=[\"input\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'input': 'List all artists.', 'query': 'SELECT * FROM Artist;'},\n", + " {'input': 'How many employees are there',\n", + " 'query': 'SELECT COUNT(*) FROM \"Employee\"'},\n", + " {'input': 'How many tracks are there in the album with ID 5?',\n", + " 'query': 'SELECT COUNT(*) FROM Track WHERE AlbumId = 5;'},\n", + " {'input': 'Which albums are from the year 2000?',\n", + " 'query': \"SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\"},\n", + " {'input': \"List all tracks in the 'Rock' genre.\",\n", + " 'query': \"SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\"}]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example_selector.select_examples({\"input\": \"how many artists are there?\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use it, we can pass the ExampleSelector directly in to our FewShotPromptTemplate:" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "prompt = FewShotPromptTemplate(\n", + " example_selector=example_selector,\n", + " example_prompt=example_prompt,\n", + " prefix=\"You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than {top_k} rows.\\n\\nHere is the relevant table info: {table_info}\\n\\nBelow are a number of examples of questions and their corresponding SQL queries.\",\n", + " suffix=\"User input: {input}\\nSQL query: \",\n", + " input_variables=[\"input\", \"top_k\", \"table_info\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than 3 rows.\n", + "\n", + "Here is the relevant table info: foo\n", + "\n", + "Below are a number of examples of questions and their corresponding SQL queries.\n", + "\n", + "User input: List all artists.\n", + "SQL query: SELECT * FROM Artist;\n", + "\n", + "User input: How many employees are there\n", + "SQL query: SELECT COUNT(*) FROM \"Employee\"\n", + "\n", + "User input: How many tracks are there in the album with ID 5?\n", + "SQL query: SELECT COUNT(*) FROM Track WHERE AlbumId = 5;\n", + "\n", + "User input: Which albums are from the year 2000?\n", + "SQL query: SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\n", + "\n", + "User input: List all tracks in the 'Rock' genre.\n", + "SQL query: SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\n", + "\n", + "User input: how many artists are there?\n", + "SQL query: \n" + ] + } + ], + "source": [ + "print(prompt.format(input=\"how many artists are there?\", top_k=3, table_info=\"foo\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'SELECT COUNT(*) FROM Artist;'" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = create_sql_query_chain(llm, db, prompt)\n", + "chain.invoke({\"question\": \"how many artists are there?\"})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/docs/use_cases/sql/query_checking.ipynb b/docs/docs/use_cases/sql/query_checking.ipynb new file mode 100644 index 0000000000000..fcb5d44a2ba30 --- /dev/null +++ b/docs/docs/use_cases/sql/query_checking.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "494149c1-9a1a-4b75-8982-6bb19cc5e14e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "4da7ae91-4973-4e97-a570-fa24024ec65d", + "metadata": {}, + "source": [ + "# Query validation\n", + "\n", + "Perhaps the most error-prone part of any SQL chain or agent is writing valid and safe SQL queries. In this guide we'll go over some strategies for validating our queries and handling invalid queries.\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d40d5bc-3647-4b5d-808a-db470d40fe7a", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "c998536a-b1ff-46e7-ac51-dc6deb55d22b", + "metadata": {}, + "source": [ + "We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71f46270-e1c6-45b4-b36e-ea2e9f860eba", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Uncomment the below to use LangSmith. Not required.\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "id": "a0a2151b-cecf-4559-92a1-ca48824fed18", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven `SQLDatabase` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8cedc936-5268-4bfa-b838-bdcc1ee9573c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\"" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM Artist LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "id": "2d203315-fab7-4621-80da-41e9bf82d803", + "metadata": {}, + "source": [ + "## Query checker\n", + "\n", + "Perhaps the simplest strategy is to ask the model itself to check the original query for common mistakes. Suppose we have the following SQL query chain:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec66bb76-b1ad-48ad-a7d4-b518e9421b86", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import create_sql_query_chain\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "chain = create_sql_query_chain(llm, db)" + ] + }, + { + "cell_type": "markdown", + "id": "da01023d-cc05-43e3-a38d-ed9d56d3ad15", + "metadata": {}, + "source": [ + "And we want to validate its outputs. We can do so by extending the chain with a second prompt and model call:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "16686750-d8ee-4c60-8d67-b28281cb6164", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "system = \"\"\"Double check the user's {dialect} query for common mistakes, including:\n", + "- Using NOT IN with NULL values\n", + "- Using UNION when UNION ALL should have been used\n", + "- Using BETWEEN for exclusive ranges\n", + "- Data type mismatch in predicates\n", + "- Properly quoting identifiers\n", + "- Using the correct number of arguments for functions\n", + "- Casting to the correct data type\n", + "- Using the proper columns for joins\n", + "\n", + "If there are any of the above mistakes, rewrite the query. If there are no mistakes, just reproduce the original query.\n", + "\n", + "Output the final SQL query only.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system), (\"human\", \"{query}\")]\n", + ").partial(dialect=db.dialect)\n", + "validation_chain = prompt | llm | StrOutputParser()\n", + "\n", + "full_chain = {\"query\": chain} | validation_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "3a910260-205d-4f4e-afc6-9477572dc947", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"SELECT AVG(Invoice.Total) AS AverageInvoice\\nFROM Invoice\\nJOIN Customer ON Invoice.CustomerId = Customer.CustomerId\\nWHERE Customer.Country = 'USA'\\nAND Customer.Fax IS NULL\\nAND Invoice.InvoiceDate >= '2003-01-01'\\nAND Invoice.InvoiceDate < '2010-01-01'\"" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query = full_chain.invoke(\n", + " {\n", + " \"question\": \"What's the average Invoice from an American customer whose Fax is missing since 2003 but before 2010\"\n", + " }\n", + ")\n", + "query" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d01d78b5-89a0-4c12-b743-707ebe64ba86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(6.632999999999998,)]'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "6e133526-26bd-49da-9cfa-7adc0e59fd72", + "metadata": {}, + "source": [ + "The obvious downside of this approach is that we need to make two model calls instead of one to generate our query. To get around this we can try to perform the query generation and query check in a single model invocation:" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "7af0030a-549e-4e69-9298-3d0a038c2fdd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m System Message \u001b[0m================================\n", + "\n", + "You are a \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m expert. Given an input question, creat a syntactically correct \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m query to run.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most \u001b[33;1m\u001b[1;3m{top_k}\u001b[0m results using the LIMIT clause as per \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Only use the following tables:\n", + "\u001b[33;1m\u001b[1;3m{table_info}\u001b[0m\n", + "\n", + "Write an initial draft of the query. Then double check the \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m query for common mistakes, including:\n", + "- Using NOT IN with NULL values\n", + "- Using UNION when UNION ALL should have been used\n", + "- Using BETWEEN for exclusive ranges\n", + "- Data type mismatch in predicates\n", + "- Properly quoting identifiers\n", + "- Using the correct number of arguments for functions\n", + "- Casting to the correct data type\n", + "- Using the proper columns for joins\n", + "\n", + "Use format:\n", + "\n", + "First draft: <<FIRST_DRAFT_QUERY>>\n", + "Final answer: <<FINAL_ANSWER_QUERY>>\n", + "\n", + "\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{input}\u001b[0m\n" + ] + } + ], + "source": [ + "system = \"\"\"You are a {dialect} expert. Given an input question, creat a syntactically correct {dialect} query to run.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the LIMIT clause as per {dialect}. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Only use the following tables:\n", + "{table_info}\n", + "\n", + "Write an initial draft of the query. Then double check the {dialect} query for common mistakes, including:\n", + "- Using NOT IN with NULL values\n", + "- Using UNION when UNION ALL should have been used\n", + "- Using BETWEEN for exclusive ranges\n", + "- Data type mismatch in predicates\n", + "- Properly quoting identifiers\n", + "- Using the correct number of arguments for functions\n", + "- Casting to the correct data type\n", + "- Using the proper columns for joins\n", + "\n", + "Use format:\n", + "\n", + "First draft: <<FIRST_DRAFT_QUERY>>\n", + "Final answer: <<FINAL_ANSWER_QUERY>>\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", \"{input}\")]).partial(dialect=db.dialect)\n", + "\n", + "def parse_final_answer(output: str) -> str:\n", + " return output.split(\"Final answer: \")[1]\n", + " \n", + "chain = create_sql_query_chain(llm, db, prompt=prompt) | parse_final_answer\n", + "prompt.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "806e27a2-e511-45ea-a4ed-8ce8fa6e1d58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\nSELECT AVG(i.Total) AS AverageInvoice\\nFROM Invoice i\\nJOIN Customer c ON i.CustomerId = c.CustomerId\\nWHERE c.Country = 'USA' AND c.Fax IS NULL AND i.InvoiceDate >= date('2003-01-01') AND i.InvoiceDate < date('2010-01-01')\"" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query = chain.invoke(\n", + " {\n", + " \"question\": \"What's the average Invoice from an American customer whose Fax is missing since 2003 but before 2010\"\n", + " }\n", + ")\n", + "query" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "70fff2fa-1f86-4f83-9fd2-e87a5234d329", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(6.632999999999998,)]'" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "fc8af115-7c23-421a-8fd7-29bf1b6687a4", + "metadata": {}, + "source": [ + "## Human-in-the-loop\n", + "\n", + "In some cases our data is sensitive enough that we never want to execute a SQL query without a human approving it first. Head to the [Tool use: Human-in-the-loop](/docs/use_cases/tool_use/human_in_the_loop) page to learn how to add a human-in-the-loop to any tool, chain or agent.\n", + "\n", + "## Error handling\n", + "\n", + "At some point, the model will make a mistake and craft an invalid SQL query. Or an issue will arise with our database. Or the model API will go down. We'll want to add some error handling behavior to our chains and agents so that we fail gracefully in these situations, and perhaps even automatically recover. To learn about error handling with tools, head to the [Tool use: Error handling](/docs/use_cases/tool_use/tool_error_handling) page." + ] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/use_cases/sql/quickstart.ipynb b/docs/docs/use_cases/sql/quickstart.ipynb new file mode 100644 index 0000000000000..490700a45197b --- /dev/null +++ b/docs/docs/use_cases/sql/quickstart.ipynb @@ -0,0 +1,603 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quickstart\n", + "\n", + "In this guide we'll go over the basic ways to create a Q&A chain and agent over a SQL database. These systems will allow us to ask a question about the data in a SQL database and get back a natural language answer. The main difference between the two is that our agent can query the database in a loop as many time as it needs to answer the question.\n", + "\n", + "## ⚠️ Security note ⚠️\n", + "\n", + "Building Q&A systems of SQL databases requires executing model-generated SQL queries. There are inherent risks in doing this. Make sure that your database connection permissions are always scoped as narrowly as possible for your chain/agent's needs. This will mitigate though not eliminate the risks of building a model-driven system. For more on general security best practices, [see here](/docs/security).\n", + "\n", + "\n", + "## Architecture\n", + "\n", + "At a high-level, the steps of any SQL chain and agent are:\n", + "\n", + "1. **Convert question to SQL query**: Model converts user input to a SQL query.\n", + "2. **Execute SQL query**: Execute the SQL query.\n", + "3. **Answer the question**: Model responds to user input using the query results.\n", + "\n", + "\n", + "![sql_usecase.png](../../../static/img/sql_usecase.png)\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-openai" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We default to OpenAI models in this guide." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Uncomment the below to use LangSmith. Not required.\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven `SQLDatabase` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\"" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM Artist LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great! We've got a SQL database that we can query. Now let's try hooking it up to an LLM.\n", + "\n", + "## Chain\n", + "\n", + "Let's create a simple chain that takes a question, turns it into a SQL query, executes the query, and uses the result to answer the original question.\n", + "\n", + "### Convert question to SQL query\n", + "\n", + "The first step in a SQL chain or agent is to take the user input and convert it to a SQL query. LangChain comes with a built-in chain for this: [create_sql_query_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.sql_database.query.create_sql_query_chain.html)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'SELECT COUNT(*) FROM Employee'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chains import create_sql_query_chain\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "chain = create_sql_query_chain(llm, db)\n", + "response = chain.invoke({\"question\": \"How many employees are there\"})\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can execute the query to make sure it's valid:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(8,)]'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can look at the [LangSmith trace](https://smith.langchain.com/public/c8fa52ea-be46-4829-bde2-52894970b830/r) to get a better understanding of what this chain is doing. We can also inpect the chain directly for its prompts. Looking at the prompt (below), we can see that it is:\n", + "\n", + "* Dialect-specific. In this case it references SQLite explicitly.\n", + "* Has definitions for all the available tables.\n", + "* Has three examples rows for each table.\n", + "\n", + "This technique is inspired by papers like [this](https://arxiv.org/pdf/2204.00498.pdf), which suggest showing examples rows and being explicit about tables improves performance. We can also in" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Use the following format:\n", + "\n", + "Question: Question here\n", + "SQLQuery: SQL Query to run\n", + "SQLResult: Result of the SQLQuery\n", + "Answer: Final answer here\n", + "\n", + "Only use the following tables:\n", + "\u001b[33;1m\u001b[1;3m{table_info}\u001b[0m\n", + "\n", + "Question: \u001b[33;1m\u001b[1;3m{input}\u001b[0m\n" + ] + } + ], + "source": [ + "chain.get_prompts()[0].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Execute SQL query\n", + "\n", + "Now that we've generated a SQL query, we'll want to execute it. **This is the most dangerous part of creating a SQL chain.** Consider carefully if it is OK to run automated queries over your data. Minimize the database connection permissions as much as possible. Consider adding a human approval step to you chains before query execution (see below).\n", + "\n", + "We can use the `QuerySQLDatabaseTool` to easily add query execution to our chain:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(8,)]'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool\n", + "\n", + "execute_query = QuerySQLDataBaseTool(db=db)\n", + "write_query = create_sql_query_chain(llm, db)\n", + "chain = write_query | execute_query\n", + "chain.invoke({\"question\": \"How many employees are there\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Answer the question\n", + "\n", + "Now that we've got a way to automatically generate and execute queries, we just need to combine the original question and SQL query result to generate a final answer. We can do this by passing question and result to the LLM once more:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'There are 8 employees.'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import PromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "answer_prompt = PromptTemplate.from_template(\n", + " \"\"\"Given the following user question, corresponding SQL query, and SQL result, answer the user question.\n", + "\n", + "Question: {question}\n", + "SQL Query: {query}\n", + "SQL Result: {result}\n", + "Answer: \"\"\"\n", + ")\n", + "\n", + "answer = answer_prompt | llm | StrOutputParser()\n", + "chain = (\n", + " RunnablePassthrough.assign(query=write_query).assign(\n", + " result=itemgetter(\"query\") | execute_query\n", + " )\n", + " | answer\n", + ")\n", + "\n", + "chain.invoke({\"question\": \"How many employees are there\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Next steps\n", + "\n", + "For more complex query-generation, we may want to create few-shot prompts or add query-checking steps. For advanced techniques like this and more check out:\n", + "\n", + "* [Prompting strategies](/docs/use_cases/sql/prompting): Advanced prompt engineering techniques.\n", + "* [Query checking](/docs/use_cases/sql/query_checking): Add query validation and error handling.\n", + "* [Large databses](/docs/use_cases/sql/large_db): Techniques for working with large databases." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Agents\n", + "\n", + "LangChain has an SQL Agent which provides a more flexible way of interacting with SQL databases. The main advantages of using the SQL Agent are:\n", + "\n", + "- It can answer questions based on the databases' schema as well as on the databases' content (like describing a specific table).\n", + "- It can recover from errors by running a generated query, catching the traceback and regenerating it correctly.\n", + "- It can answer questions that require multiple dependent queries.\n", + "\n", + "To initialize the agent, we use `create_sql_agent` function. This agent contains the `SQLDatabaseToolkit` which contains tools to: \n", + "\n", + "* Create and execute queries\n", + "* Check query syntax\n", + "* Retrieve table descriptions\n", + "* ... and more\n", + "\n", + "### Initializing agent" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.agent_toolkits import create_sql_agent\n", + "\n", + "agent_executor = create_sql_agent(llm, db=db, agent_type=\"openai-tools\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_list_tables` with `{}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_schema` with `Invoice,Customer`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m\n", + "CREATE TABLE \"Customer\" (\n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"FirstName\" NVARCHAR(40) NOT NULL, \n", + "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", + "\t\"Company\" NVARCHAR(80), \n", + "\t\"Address\" NVARCHAR(70), \n", + "\t\"City\" NVARCHAR(40), \n", + "\t\"State\" NVARCHAR(40), \n", + "\t\"Country\" NVARCHAR(40), \n", + "\t\"PostalCode\" NVARCHAR(10), \n", + "\t\"Phone\" NVARCHAR(24), \n", + "\t\"Fax\" NVARCHAR(24), \n", + "\t\"Email\" NVARCHAR(60) NOT NULL, \n", + "\t\"SupportRepId\" INTEGER, \n", + "\tPRIMARY KEY (\"CustomerId\"), \n", + "\tFOREIGN KEY(\"SupportRepId\") REFERENCES \"Employee\" (\"EmployeeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Customer table:\n", + "CustomerId\tFirstName\tLastName\tCompany\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\tSupportRepId\n", + "1\tLuís\tGonçalves\tEmbraer - Empresa Brasileira de Aeronáutica S.A.\tAv. Brigadeiro Faria Lima, 2170\tSão José dos Campos\tSP\tBrazil\t12227-000\t+55 (12) 3923-5555\t+55 (12) 3923-5566\tluisg@embraer.com.br\t3\n", + "2\tLeonie\tKöhler\tNone\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t+49 0711 2842222\tNone\tleonekohler@surfeu.de\t5\n", + "3\tFrançois\tTremblay\tNone\t1498 rue Bélanger\tMontréal\tQC\tCanada\tH2G 1A7\t+1 (514) 721-4711\tNone\tftremblay@gmail.com\t3\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Invoice\" (\n", + "\t\"InvoiceId\" INTEGER NOT NULL, \n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"InvoiceDate\" DATETIME NOT NULL, \n", + "\t\"BillingAddress\" NVARCHAR(70), \n", + "\t\"BillingCity\" NVARCHAR(40), \n", + "\t\"BillingState\" NVARCHAR(40), \n", + "\t\"BillingCountry\" NVARCHAR(40), \n", + "\t\"BillingPostalCode\" NVARCHAR(10), \n", + "\t\"Total\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"InvoiceId\"), \n", + "\tFOREIGN KEY(\"CustomerId\") REFERENCES \"Customer\" (\"CustomerId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Invoice table:\n", + "InvoiceId\tCustomerId\tInvoiceDate\tBillingAddress\tBillingCity\tBillingState\tBillingCountry\tBillingPostalCode\tTotal\n", + "1\t2\t2009-01-01 00:00:00\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t1.98\n", + "2\t4\t2009-01-02 00:00:00\tUllevålsveien 14\tOslo\tNone\tNorway\t0171\t3.96\n", + "3\t8\t2009-01-03 00:00:00\tGrétrystraat 63\tBrussels\tNone\tBelgium\t1000\t5.94\n", + "*/\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_query` with `SELECT c.Country, SUM(i.Total) AS TotalSales FROM Invoice i JOIN Customer c ON i.CustomerId = c.CustomerId GROUP BY c.Country ORDER BY TotalSales DESC LIMIT 10;`\n", + "responded: To list the total sales per country, I can query the \"Invoice\" and \"Customer\" tables. I will join these tables on the \"CustomerId\" column and group the results by the \"BillingCountry\" column. Then, I will calculate the sum of the \"Total\" column to get the total sales per country. Finally, I will order the results in descending order of the total sales.\n", + "\n", + "Here is the SQL query:\n", + "\n", + "```sql\n", + "SELECT c.Country, SUM(i.Total) AS TotalSales\n", + "FROM Invoice i\n", + "JOIN Customer c ON i.CustomerId = c.CustomerId\n", + "GROUP BY c.Country\n", + "ORDER BY TotalSales DESC\n", + "LIMIT 10;\n", + "```\n", + "\n", + "Now, I will execute this query to get the total sales per country.\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[('USA', 523.0600000000003), ('Canada', 303.9599999999999), ('France', 195.09999999999994), ('Brazil', 190.09999999999997), ('Germany', 156.48), ('United Kingdom', 112.85999999999999), ('Czech Republic', 90.24000000000001), ('Portugal', 77.23999999999998), ('India', 75.25999999999999), ('Chile', 46.62)]\u001b[0m\u001b[32;1m\u001b[1;3mThe total sales per country are as follows:\n", + "\n", + "1. USA: $523.06\n", + "2. Canada: $303.96\n", + "3. France: $195.10\n", + "4. Brazil: $190.10\n", + "5. Germany: $156.48\n", + "6. United Kingdom: $112.86\n", + "7. Czech Republic: $90.24\n", + "8. Portugal: $77.24\n", + "9. India: $75.26\n", + "10. Chile: $46.62\n", + "\n", + "To answer the second question, the country whose customers spent the most is the USA, with a total sales of $523.06.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': \"List the total sales per country. Which country's customers spent the most?\",\n", + " 'output': 'The total sales per country are as follows:\\n\\n1. USA: $523.06\\n2. Canada: $303.96\\n3. France: $195.10\\n4. Brazil: $190.10\\n5. Germany: $156.48\\n6. United Kingdom: $112.86\\n7. Czech Republic: $90.24\\n8. Portugal: $77.24\\n9. India: $75.26\\n10. Chile: $46.62\\n\\nTo answer the second question, the country whose customers spent the most is the USA, with a total sales of $523.06.'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"List the total sales per country. Which country's customers spent the most?\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_list_tables` with `{}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3mAlbum, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_schema` with `PlaylistTrack`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m\n", + "CREATE TABLE \"PlaylistTrack\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from PlaylistTrack table:\n", + "PlaylistId\tTrackId\n", + "1\t3402\n", + "1\t3389\n", + "1\t3390\n", + "*/\u001b[0m\u001b[32;1m\u001b[1;3mThe `PlaylistTrack` table has two columns: `PlaylistId` and `TrackId`. It is a junction table that represents the many-to-many relationship between playlists and tracks. \n", + "\n", + "Here is the schema of the `PlaylistTrack` table:\n", + "\n", + "```\n", + "CREATE TABLE \"PlaylistTrack\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + ")\n", + "```\n", + "\n", + "The `PlaylistId` column is a foreign key referencing the `PlaylistId` column in the `Playlist` table. The `TrackId` column is a foreign key referencing the `TrackId` column in the `Track` table.\n", + "\n", + "Here are three sample rows from the `PlaylistTrack` table:\n", + "\n", + "```\n", + "PlaylistId TrackId\n", + "1 3402\n", + "1 3389\n", + "1 3390\n", + "```\n", + "\n", + "Please let me know if there is anything else I can help with.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Describe the playlisttrack table',\n", + " 'output': 'The `PlaylistTrack` table has two columns: `PlaylistId` and `TrackId`. It is a junction table that represents the many-to-many relationship between playlists and tracks. \\n\\nHere is the schema of the `PlaylistTrack` table:\\n\\n```\\nCREATE TABLE \"PlaylistTrack\" (\\n\\t\"PlaylistId\" INTEGER NOT NULL, \\n\\t\"TrackId\" INTEGER NOT NULL, \\n\\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \\n\\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \\n\\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\\n)\\n```\\n\\nThe `PlaylistId` column is a foreign key referencing the `PlaylistId` column in the `Playlist` table. The `TrackId` column is a foreign key referencing the `TrackId` column in the `Track` table.\\n\\nHere are three sample rows from the `PlaylistTrack` table:\\n\\n```\\nPlaylistId TrackId\\n1 3402\\n1 3389\\n1 3390\\n```\\n\\nPlease let me know if there is anything else I can help with.'}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke({\"input\": \"Describe the playlisttrack table\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Next steps\n", + "\n", + "For more on how to use and customize agents head to the [Agents](/docs/use_cases/sql/agents) page." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv", + "language": "python", + "name": "poetry-venv" + }, + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/vercel.json b/docs/vercel.json index 41ae1811d6698..11ef901e656f8 100644 --- a/docs/vercel.json +++ b/docs/vercel.json @@ -1,5 +1,9 @@ { "redirects": [ + { + "source": "/docs/use_cases/qa_structured/sql", + "destination": "/docs/use_cases/sql/" + }, { "source": "/docs/contributing/packages", "destination": "/docs/packages" @@ -294,7 +298,7 @@ }, { "source": "/docs/use_cases/qa_structured/integrations/sqlite", - "destination": "/cookbook" + "destination": "/docs/use_cases/sql/" }, { "source": "/docs/use_cases/more/graph/tot", @@ -1418,7 +1422,7 @@ }, { "source": "/docs/integrations/tools/sqlite", - "destination": "/docs/use_cases/qa_structured/sql" + "destination": "/docs/use_cases/sql" }, { "source": "/en/latest/modules/callbacks/filecallbackhandler.html", @@ -3506,11 +3510,7 @@ }, { "source": "/en/latest/use_cases/tabular.html", - "destination": "/docs/use_cases/qa_structured" - }, - { - "source": "/docs/use_cases/sql(/?)", - "destination": "/docs/use_cases/qa_structured/sql" + "destination": "/docs/use_cases/sql" }, { "source": "/en/latest/youtube.html", diff --git a/libs/community/langchain_community/agent_toolkits/sql/base.py b/libs/community/langchain_community/agent_toolkits/sql/base.py index c2451f7c23162..66c3c57dc6e4c 100644 --- a/libs/community/langchain_community/agent_toolkits/sql/base.py +++ b/libs/community/langchain_community/agent_toolkits/sql/base.py @@ -1,11 +1,11 @@ """SQL agent.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence +import warnings +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Sequence, Union -from langchain_core.callbacks import BaseCallbackManager -from langchain_core.language_models import BaseLanguageModel from langchain_core.messages import AIMessage, SystemMessage +from langchain_core.prompts import BasePromptTemplate, PromptTemplate from langchain_core.prompts.chat import ( ChatPromptTemplate, HumanMessagePromptTemplate, @@ -15,22 +15,29 @@ from langchain_community.agent_toolkits.sql.prompt import ( SQL_FUNCTIONS_SUFFIX, SQL_PREFIX, - SQL_SUFFIX, ) from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit -from langchain_community.tools import BaseTool +from langchain_community.tools.sql_database.tool import ( + InfoSQLDatabaseTool, + ListSQLDatabaseTool, +) if TYPE_CHECKING: from langchain.agents.agent import AgentExecutor from langchain.agents.agent_types import AgentType + from langchain_core.callbacks import BaseCallbackManager + from langchain_core.language_models import BaseLanguageModel + from langchain_core.tools import BaseTool + + from langchain_community.utilities.sql_database import SQLDatabase def create_sql_agent( llm: BaseLanguageModel, - toolkit: SQLDatabaseToolkit, - agent_type: Optional[AgentType] = None, + toolkit: Optional[SQLDatabaseToolkit] = None, + agent_type: Optional[Union[AgentType, Literal["openai-tools"]]] = None, callback_manager: Optional[BaseCallbackManager] = None, - prefix: str = SQL_PREFIX, + prefix: Optional[str] = None, suffix: Optional[str] = None, format_instructions: Optional[str] = None, input_variables: Optional[List[str]] = None, @@ -41,62 +48,165 @@ def create_sql_agent( verbose: bool = False, agent_executor_kwargs: Optional[Dict[str, Any]] = None, extra_tools: Sequence[BaseTool] = (), + *, + db: Optional[SQLDatabase] = None, + prompt: Optional[BasePromptTemplate] = None, **kwargs: Any, ) -> AgentExecutor: - """Construct an SQL agent from an LLM and tools.""" - from langchain.agents.agent import AgentExecutor, BaseSingleActionAgent + """Construct a SQL agent from an LLM and toolkit or database. + + Args: + llm: Language model to use for the agent. + toolkit: SQLDatabaseToolkit for the agent to use. Must provide exactly one of + 'toolkit' or 'db'. Specify 'toolkit' if you want to use a different model + for the agent and the toolkit. + agent_type: One of "openai-tools", "openai-functions", or + "zero-shot-react-description". Defaults to "zero-shot-react-description". + "openai-tools" is recommended over "openai-functions". + callback_manager: DEPRECATED. Pass "callbacks" key into 'agent_executor_kwargs' + instead to pass constructor callbacks to AgentExecutor. + prefix: Prompt prefix string. Must contain variables "top_k" and "dialect". + suffix: Prompt suffix string. Default depends on agent type. + format_instructions: Formatting instructions to pass to + ZeroShotAgent.create_prompt() when 'agent_type' is + "zero-shot-react-description". Otherwise ignored. + input_variables: DEPRECATED. Input variables to explicitly specify as part of + ZeroShotAgent.create_prompt() when 'agent_type' is + "zero-shot-react-description". Otherwise ignored. + top_k: Number of rows to query for by default. + max_iterations: Passed to AgentExecutor init. + max_execution_time: Passed to AgentExecutor init. + early_stopping_method: Passed to AgentExecutor init. + verbose: AgentExecutor verbosity. + agent_executor_kwargs: Arbitrary additional AgentExecutor args. + extra_tools: Additional tools to give to agent on top of the ones that come with + SQLDatabaseToolkit. + db: SQLDatabase from which to create a SQLDatabaseToolkit. Toolkit is created + using 'db' and 'llm'. Must provide exactly one of 'db' or 'toolkit'. + prompt: Complete agent prompt. prompt and {prefix, suffix, format_instructions, + input_variables} are mutually exclusive. + **kwargs: DEPRECATED. Not used, kept for backwards compatibility. + + Returns: + An AgentExecutor with the specified agent_type agent. + + Example: + + .. code-block:: python + + from langchain_openai import ChatOpenAI + from langchain_community.agent_toolkits import create_sql_agent + from langchain_community.utilities import SQLDatabase + + db = SQLDatabase.from_uri("sqlite:///Chinook.db") + llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) + agent_executor = create_sql_agent(llm, db=db, agent_type="openai-tools", verbose=True) + + """ # noqa: E501 + from langchain.agents import ( + create_openai_functions_agent, + create_openai_tools_agent, + create_react_agent, + ) + from langchain.agents.agent import ( + AgentExecutor, + RunnableAgent, + RunnableMultiActionAgent, + ) from langchain.agents.agent_types import AgentType - from langchain.agents.mrkl.base import ZeroShotAgent - from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent - from langchain.chains.llm import LLMChain + if toolkit is None and db is None: + raise ValueError( + "Must provide exactly one of 'toolkit' or 'db'. Received neither." + ) + if toolkit and db: + raise ValueError( + "Must provide exactly one of 'toolkit' or 'db'. Received both." + ) + if kwargs: + warnings.warn( + f"Received additional kwargs {kwargs} which are no longer supported." + ) + + toolkit = toolkit or SQLDatabaseToolkit(llm=llm, db=db) agent_type = agent_type or AgentType.ZERO_SHOT_REACT_DESCRIPTION tools = toolkit.get_tools() + list(extra_tools) - prefix = prefix.format(dialect=toolkit.dialect, top_k=top_k) - agent: BaseSingleActionAgent + if prompt is None: + prefix = prefix or SQL_PREFIX + prefix = prefix.format(dialect=toolkit.dialect, top_k=top_k) + else: + if "top_k" in prompt.input_variables: + prompt = prompt.partial(top_k=str(top_k)) + if "dialect" in prompt.input_variables: + prompt = prompt.partial(dialect=toolkit.dialect) + db_context = toolkit.get_context() + if "table_info" in prompt.input_variables: + prompt = prompt.partial(table_info=db_context["table_info"]) + tools = [ + tool for tool in tools if not isinstance(tool, InfoSQLDatabaseTool) + ] + if "table_names" in prompt.input_variables: + prompt = prompt.partial(table_names=db_context["table_names"]) + tools = [ + tool for tool in tools if not isinstance(tool, ListSQLDatabaseTool) + ] if agent_type == AgentType.ZERO_SHOT_REACT_DESCRIPTION: - prompt_params = ( - {"format_instructions": format_instructions} - if format_instructions is not None - else {} - ) - prompt = ZeroShotAgent.create_prompt( - tools, - prefix=prefix, - suffix=suffix or SQL_SUFFIX, - input_variables=input_variables, - **prompt_params, - ) - llm_chain = LLMChain( - llm=llm, - prompt=prompt, - callback_manager=callback_manager, + if prompt is None: + from langchain.agents.mrkl import prompt as react_prompt + + format_instructions = ( + format_instructions or react_prompt.FORMAT_INSTRUCTIONS + ) + template = "\n\n".join( + [ + react_prompt.PREFIX, + "{tools}", + format_instructions, + react_prompt.SUFFIX, + ] + ) + prompt = PromptTemplate.from_template(template) + agent = RunnableAgent( + runnable=create_react_agent(llm, tools, prompt), + input_keys_arg=["input"], + return_keys_arg=["output"], ) - tool_names = [tool.name for tool in tools] - agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) elif agent_type == AgentType.OPENAI_FUNCTIONS: - messages = [ - SystemMessage(content=prefix), - HumanMessagePromptTemplate.from_template("{input}"), - AIMessage(content=suffix or SQL_FUNCTIONS_SUFFIX), - MessagesPlaceholder(variable_name="agent_scratchpad"), - ] - input_variables = ["input", "agent_scratchpad"] - _prompt = ChatPromptTemplate(input_variables=input_variables, messages=messages) - - agent = OpenAIFunctionsAgent( - llm=llm, - prompt=_prompt, - tools=tools, - callback_manager=callback_manager, - **kwargs, + if prompt is None: + messages = [ + SystemMessage(content=prefix), + HumanMessagePromptTemplate.from_template("{input}"), + AIMessage(content=suffix or SQL_FUNCTIONS_SUFFIX), + MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + prompt = ChatPromptTemplate.from_messages(messages) + agent = RunnableAgent( + runnable=create_openai_functions_agent(llm, tools, prompt), + input_keys_arg=["input"], + return_keys_arg=["output"], ) + elif agent_type == "openai-tools": + if prompt is None: + messages = [ + SystemMessage(content=prefix), + HumanMessagePromptTemplate.from_template("{input}"), + AIMessage(content=suffix or SQL_FUNCTIONS_SUFFIX), + MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + prompt = ChatPromptTemplate.from_messages(messages) + agent = RunnableMultiActionAgent( + runnable=create_openai_tools_agent(llm, tools, prompt), + input_keys_arg=["input"], + return_keys_arg=["output"], + ) + else: raise ValueError(f"Agent type {agent_type} not supported at the moment.") - return AgentExecutor.from_agent_and_tools( + return AgentExecutor( + name="SQL Agent Executor", agent=agent, tools=tools, callback_manager=callback_manager, diff --git a/libs/community/langchain_community/agent_toolkits/sql/toolkit.py b/libs/community/langchain_community/agent_toolkits/sql/toolkit.py index 382fcbb485653..6944b557f20dd 100644 --- a/libs/community/langchain_community/agent_toolkits/sql/toolkit.py +++ b/libs/community/langchain_community/agent_toolkits/sql/toolkit.py @@ -69,3 +69,7 @@ def get_tools(self) -> List[BaseTool]: list_sql_database_tool, query_sql_checker_tool, ] + + def get_context(self) -> dict: + """Return db context that you may want in agent prompt.""" + return self.db.get_context() diff --git a/libs/community/langchain_community/utilities/sql_database.py b/libs/community/langchain_community/utilities/sql_database.py index 8379fbc337a9b..e56423733cb29 100644 --- a/libs/community/langchain_community/utilities/sql_database.py +++ b/libs/community/langchain_community/utilities/sql_database.py @@ -1,10 +1,10 @@ """SQLAlchemy wrapper around a database.""" from __future__ import annotations -import warnings from typing import Any, Dict, Iterable, List, Literal, Optional, Sequence import sqlalchemy +from langchain_core._api import deprecated from langchain_core.utils import get_from_env from sqlalchemy import MetaData, Table, create_engine, inspect, select, text from sqlalchemy.engine import Engine @@ -272,11 +272,9 @@ def get_usable_table_names(self) -> Iterable[str]: return sorted(self._include_tables) return sorted(self._all_tables - self._ignore_tables) + @deprecated("0.0.1", alternative="get_usable_table_name", removal="0.2.0") def get_table_names(self) -> Iterable[str]: """Get names of tables available.""" - warnings.warn( - "This method is deprecated - please use `get_usable_table_names`." - ) return self.get_usable_table_names() @property @@ -487,3 +485,9 @@ def run_no_throw( except SQLAlchemyError as e: """Format the error message""" return f"Error: {e}" + + def get_context(self) -> Dict[str, Any]: + """Return db context that you may want in agent prompt.""" + table_names = list(self.get_usable_table_names()) + table_info = self.get_table_info_no_throw() + return {"table_info": table_info, "table_names": ", ".join(table_names)} diff --git a/libs/core/langchain_core/example_selectors/semantic_similarity.py b/libs/core/langchain_core/example_selectors/semantic_similarity.py index 243c665d69035..919ad88a3b588 100644 --- a/libs/core/langchain_core/example_selectors/semantic_similarity.py +++ b/libs/core/langchain_core/example_selectors/semantic_similarity.py @@ -74,6 +74,9 @@ def from_examples( vectorstore_cls: Type[VectorStore], k: int = 4, input_keys: Optional[List[str]] = None, + *, + example_keys: Optional[List[str]] = None, + vectorstore_kwargs: Optional[dict] = None, **vectorstore_cls_kwargs: Any, ) -> SemanticSimilarityExampleSelector: """Create k-shot example selector using example list and embeddings. @@ -102,7 +105,13 @@ def from_examples( vectorstore = vectorstore_cls.from_texts( string_examples, embeddings, metadatas=examples, **vectorstore_cls_kwargs ) - return cls(vectorstore=vectorstore, k=k, input_keys=input_keys) + return cls( + vectorstore=vectorstore, + k=k, + input_keys=input_keys, + example_keys=example_keys, + vectorstore_kwargs=vectorstore_kwargs, + ) class MaxMarginalRelevanceExampleSelector(SemanticSimilarityExampleSelector): diff --git a/libs/langchain/langchain/agents/agent.py b/libs/langchain/langchain/agents/agent.py index 4bfcde68e0c83..187a3ba0e61a4 100644 --- a/libs/langchain/langchain/agents/agent.py +++ b/libs/langchain/langchain/agents/agent.py @@ -343,8 +343,8 @@ class RunnableAgent(BaseSingleActionAgent): runnable: Runnable[dict, Union[AgentAction, AgentFinish]] """Runnable to call to get agent action.""" - _input_keys: List[str] = [] - """Input keys.""" + input_keys_arg: List[str] = [] + return_keys_arg: List[str] = [] class Config: """Configuration for this pydantic object.""" @@ -354,16 +354,11 @@ class Config: @property def return_values(self) -> List[str]: """Return values of the agent.""" - return [] + return self.return_keys_arg @property def input_keys(self) -> List[str]: - """Return the input keys. - - Returns: - List of input keys. - """ - return self._input_keys + return self.input_keys_arg def plan( self, @@ -439,8 +434,8 @@ class RunnableMultiActionAgent(BaseMultiActionAgent): runnable: Runnable[dict, Union[List[AgentAction], AgentFinish]] """Runnable to call to get agent actions.""" - _input_keys: List[str] = [] - """Input keys.""" + input_keys_arg: List[str] = [] + return_keys_arg: List[str] = [] class Config: """Configuration for this pydantic object.""" @@ -450,7 +445,7 @@ class Config: @property def return_values(self) -> List[str]: """Return values of the agent.""" - return [] + return self.return_keys_arg @property def input_keys(self) -> List[str]: @@ -459,7 +454,7 @@ def input_keys(self) -> List[str]: Returns: List of input keys. """ - return self._input_keys + return self.input_keys_arg def plan( self, diff --git a/libs/langchain/langchain/agents/mrkl/base.py b/libs/langchain/langchain/agents/mrkl/base.py index 406390d3f8ed7..976ecf66aaedd 100644 --- a/libs/langchain/langchain/agents/mrkl/base.py +++ b/libs/langchain/langchain/agents/mrkl/base.py @@ -83,9 +83,9 @@ def create_prompt( tool_names = ", ".join([tool.name for tool in tools]) format_instructions = format_instructions.format(tool_names=tool_names) template = "\n\n".join([prefix, tool_strings, format_instructions, suffix]) - if input_variables is None: - input_variables = ["input", "agent_scratchpad"] - return PromptTemplate(template=template, input_variables=input_variables) + if input_variables: + return PromptTemplate(template=template, input_variables=input_variables) + return PromptTemplate.from_template(template) @classmethod def from_llm_and_tools( diff --git a/libs/langchain/langchain/chains/base.py b/libs/langchain/langchain/chains/base.py index 2afcac43b8519..dc665ae9978c4 100644 --- a/libs/langchain/langchain/chains/base.py +++ b/libs/langchain/langchain/chains/base.py @@ -131,7 +131,7 @@ def invoke( callbacks = config.get("callbacks") tags = config.get("tags") metadata = config.get("metadata") - run_name = config.get("run_name") + run_name = config.get("run_name") or self.get_name() include_run_info = kwargs.get("include_run_info", False) return_only_outputs = kwargs.get("return_only_outputs", False) @@ -178,7 +178,7 @@ async def ainvoke( callbacks = config.get("callbacks") tags = config.get("tags") metadata = config.get("metadata") - run_name = config.get("run_name") + run_name = config.get("run_name") or self.get_name() include_run_info = kwargs.get("include_run_info", False) return_only_outputs = kwargs.get("return_only_outputs", False) diff --git a/libs/langchain/langchain/chains/sql_database/query.py b/libs/langchain/langchain/chains/sql_database/query.py index 63a61fc102277..6bd074c716605 100644 --- a/libs/langchain/langchain/chains/sql_database/query.py +++ b/libs/langchain/langchain/chains/sql_database/query.py @@ -1,10 +1,10 @@ -from typing import List, Optional, TypedDict, Union +from typing import Any, Dict, List, Optional, TypedDict, Union from langchain_community.utilities.sql_database import SQLDatabase from langchain_core.language_models import BaseLanguageModel from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import BasePromptTemplate -from langchain_core.runnables import Runnable, RunnableParallel +from langchain_core.runnables import Runnable, RunnablePassthrough from langchain.chains.sql_database.prompt import PROMPT, SQL_PROMPTS @@ -31,7 +31,7 @@ def create_sql_query_chain( db: SQLDatabase, prompt: Optional[BasePromptTemplate] = None, k: int = 5, -) -> Runnable[Union[SQLInput, SQLInputWithTables], str]: +) -> Runnable[Union[SQLInput, SQLInputWithTables, Dict[str, Any]], str]: """Create a chain that generates SQL queries. *Security Note*: This chain generates SQL queries for the given database. @@ -50,34 +50,93 @@ def create_sql_query_chain( See https://python.langchain.com/docs/security for more information. Args: - llm: The language model to use - db: The SQLDatabase to generate the query for + llm: The language model to use. + db: The SQLDatabase to generate the query for. prompt: The prompt to use. If none is provided, will choose one - based on dialect. Defaults to None. + based on dialect. Defaults to None. See Prompt section below for more. k: The number of results per select statement to return. Defaults to 5. Returns: A chain that takes in a question and generates a SQL query that answers that question. - """ + + Example: + + .. code-block:: python + + # pip install -U langchain langchain-community langchain-openai + from langchain_openai import ChatOpenAI + from langchain.chains import create_sql_query_chain + from langchain_community.utilities import SQLDatabase + + db = SQLDatabase.from_uri("sqlite:///Chinook.db") + llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) + chain = create_sql_query_chain(llm, db) + response = chain.invoke({"question": "How many employees are there"}) + + Prompt: + If no prompt is provided, a default prompt is selected based on the SQLDatabase dialect. If one is provided, it must support input variables: + * input: The user question plus suffix "\nSQLQuery: " is passed here. + * top_k: The number of results per select statement (the `k` argument to + this function) is passed in here. + * table_info: Table definitions and sample rows are passed in here. If the + user specifies "table_names_to_use" when invoking chain, only those + will be included. Otherwise, all tables are included. + * dialect (optional): If dialect input variable is in prompt, the db + dialect will be passed in here. + + Here's an example prompt: + + .. code-block:: python + + from langchain_core.prompts import PromptTemplate + + template = '''Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. + Use the following format: + + Question: "Question here" + SQLQuery: "SQL Query to run" + SQLResult: "Result of the SQLQuery" + Answer: "Final answer here" + + Only use the following tables: + + {table_info}. + + Question: {input}''' + prompt = PromptTemplate.from_template(template) + """ # noqa: E501 if prompt is not None: prompt_to_use = prompt elif db.dialect in SQL_PROMPTS: prompt_to_use = SQL_PROMPTS[db.dialect] else: prompt_to_use = PROMPT + if {"input", "top_k", "table_info"}.difference(prompt_to_use.input_variables): + raise ValueError( + f"Prompt must have input variables: 'input', 'top_k', " + f"'table_info'. Received prompt with input variables: " + f"{prompt_to_use.input_variables}. Full prompt:\n\n{prompt_to_use}" + ) + if "dialect" in prompt_to_use.input_variables: + prompt_to_use = prompt_to_use.partial(dialect=db.dialect) + inputs = { "input": lambda x: x["question"] + "\nSQLQuery: ", - "top_k": lambda _: k, "table_info": lambda x: db.get_table_info( table_names=x.get("table_names_to_use") ), } - if "dialect" in prompt_to_use.input_variables: - inputs["dialect"] = lambda _: (db.dialect, prompt_to_use) return ( - RunnableParallel(inputs) - | prompt_to_use + RunnablePassthrough.assign(**inputs) # type: ignore + | ( + lambda x: { + k: v + for k, v in x.items() + if k not in ("question", "table_names_to_use") + } + ) + | prompt_to_use.partial(top_k=str(k)) | llm.bind(stop=["\nSQLResult:"]) | StrOutputParser() | _strip diff --git a/libs/langchain/langchain/tools/retriever.py b/libs/langchain/langchain/tools/retriever.py index a4ed55e96b9b4..1d2cc7bc27248 100644 --- a/libs/langchain/langchain/tools/retriever.py +++ b/libs/langchain/langchain/tools/retriever.py @@ -1,3 +1,7 @@ +from functools import partial +from typing import Optional + +from langchain_core.prompts import BasePromptTemplate, PromptTemplate, format_document from langchain_core.pydantic_v1 import BaseModel, Field from langchain_core.retrievers import BaseRetriever @@ -10,8 +14,37 @@ class RetrieverInput(BaseModel): query: str = Field(description="query to look up in retriever") +def _get_relevant_documents( + query: str, + retriever: BaseRetriever, + document_prompt: BasePromptTemplate, + document_separator: str, +) -> str: + docs = retriever.get_relevant_documents(query) + return document_separator.join( + format_document(doc, document_prompt) for doc in docs + ) + + +async def _aget_relevant_documents( + query: str, + retriever: BaseRetriever, + document_prompt: BasePromptTemplate, + document_separator: str, +) -> str: + docs = await retriever.aget_relevant_documents(query) + return document_separator.join( + format_document(doc, document_prompt) for doc in docs + ) + + def create_retriever_tool( - retriever: BaseRetriever, name: str, description: str + retriever: BaseRetriever, + name: str, + description: str, + *, + document_prompt: Optional[BasePromptTemplate] = None, + document_separator: str = "\n\n", ) -> Tool: """Create a tool to do retrieval of documents. @@ -25,10 +58,23 @@ def create_retriever_tool( Returns: Tool class to pass to an agent """ + document_prompt = document_prompt or PromptTemplate.from_template("{page_content}") + func = partial( + _get_relevant_documents, + retriever=retriever, + document_prompt=document_prompt, + document_separator=document_separator, + ) + afunc = partial( + _aget_relevant_documents, + retriever=retriever, + document_prompt=document_prompt, + document_separator=document_separator, + ) return Tool( name=name, description=description, - func=retriever.get_relevant_documents, - coroutine=retriever.aget_relevant_documents, + func=func, + coroutine=afunc, args_schema=RetrieverInput, ) From 9cf0f5eb785c7b8b965ab9792e2b3beba5412635 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:28:03 -0800 Subject: [PATCH 137/215] core[patch]: Release 0.1.14 (#16382) --- libs/core/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index c2860cf974114..c5eb14fbe2a6e 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-core" -version = "0.1.13" +version = "0.1.14" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From 87790138474312baa2b4cc27e30b4522d0631a73 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:50:19 -0800 Subject: [PATCH 138/215] community[patch]: Release 0.0.14 (#16384) --- libs/community/_test_minimum_requirements.txt | 2 +- libs/community/poetry.lock | 40 ++++++++++++++++--- libs/community/pyproject.toml | 4 +- .../callbacks/test_callback_manager.py | 1 + 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/libs/community/_test_minimum_requirements.txt b/libs/community/_test_minimum_requirements.txt index 052ee52661d78..ce12ce8c6a6b7 100644 --- a/libs/community/_test_minimum_requirements.txt +++ b/libs/community/_test_minimum_requirements.txt @@ -1 +1 @@ -langchain-core==0.1.9 \ No newline at end of file +langchain-core==0.1.14 diff --git a/libs/community/poetry.lock b/libs/community/poetry.lock index b56f0701c6a6c..9254690bb093f 100644 --- a/libs/community/poetry.lock +++ b/libs/community/poetry.lock @@ -1173,6 +1173,7 @@ files = [ {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, @@ -1181,6 +1182,7 @@ files = [ {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, @@ -1189,6 +1191,7 @@ files = [ {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, @@ -1197,6 +1200,7 @@ files = [ {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, @@ -3881,7 +3885,7 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.10" +version = "0.1.14" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -3891,7 +3895,7 @@ develop = true [package.dependencies] anyio = ">=3,<5" jsonpatch = "^1.33" -langsmith = "~0.0.63" +langsmith = "^0.0.83" packaging = "^23.2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -3907,13 +3911,13 @@ url = "../core" [[package]] name = "langsmith" -version = "0.0.69" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.69-py3-none-any.whl", hash = "sha256:49a2546bb83eedb0552673cf81a068bb08078d6d48471f4f1018e1d5c6aa46b1"}, - {file = "langsmith-0.0.69.tar.gz", hash = "sha256:8fb5297f274db0576ec650d9bab0319acfbb6622d62bc5bb9fe31c6235dc0358"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -4128,6 +4132,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -6687,6 +6701,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -6694,8 +6709,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -6712,6 +6734,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -6719,6 +6742,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -7690,7 +7714,9 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"}, @@ -7727,7 +7753,9 @@ files = [ {file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"}, @@ -9144,4 +9172,4 @@ extended-testing = ["aiosqlite", "aleph-alpha-client", "anthropic", "arxiv", "as [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "edadd024e8b2b4a817a90336013a1d92be102d03d4c41fbf5ac137f16d97fdfb" +content-hash = "3b7748a2cbf7b875397483cfdee5e695f447880dffc6c90c67d92de9cc04a7bb" diff --git a/libs/community/pyproject.toml b/libs/community/pyproject.toml index c7b01b6217c7a..bb9b97edb1e7b 100644 --- a/libs/community/pyproject.toml +++ b/libs/community/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-community" -version = "0.0.13" +version = "0.0.14" description = "Community contributed LangChain integrations." authors = [] license = "MIT" @@ -9,7 +9,7 @@ repository = "https://github.com/langchain-ai/langchain" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" -langchain-core = ">=0.1.9,<0.2" +langchain-core = ">=0.1.14,<0.2" SQLAlchemy = ">=1.4,<3" requests = "^2" PyYAML = ">=5.3" diff --git a/libs/community/tests/unit_tests/callbacks/test_callback_manager.py b/libs/community/tests/unit_tests/callbacks/test_callback_manager.py index b02b7c57dfad0..353a72c85b0f5 100644 --- a/libs/community/tests/unit_tests/callbacks/test_callback_manager.py +++ b/libs/community/tests/unit_tests/callbacks/test_callback_manager.py @@ -16,6 +16,7 @@ def test_callback_manager_configure_context_vars( """Test callback manager configuration.""" monkeypatch.setenv("LANGCHAIN_TRACING_V2", "true") monkeypatch.setenv("LANGCHAIN_TRACING", "false") + monkeypatch.setenv("LANGCHAIN_API_KEY", "foo") with patch.object(LangChainTracer, "_update_run_single"): with patch.object(LangChainTracer, "_persist_run_single"): with trace_as_chain_group("test") as group_manager: From af9f1738ca54da92087e095b5c2c39f6e4501039 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:32:24 -0800 Subject: [PATCH 139/215] langchain[patch]: Release 0.1.2 (#16388) --- libs/langchain/_test_minimum_requirements.txt | 4 ++-- libs/langchain/poetry.lock | 22 ++++++++++++++----- libs/langchain/pyproject.toml | 6 ++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/libs/langchain/_test_minimum_requirements.txt b/libs/langchain/_test_minimum_requirements.txt index a370010215cb0..464405b7ec9ff 100644 --- a/libs/langchain/_test_minimum_requirements.txt +++ b/libs/langchain/_test_minimum_requirements.txt @@ -1,2 +1,2 @@ -langchain-core==0.1.9 -langchain-community==0.0.13 +langchain-core==0.1.14 +langchain-community==0.0.14 diff --git a/libs/langchain/poetry.lock b/libs/langchain/poetry.lock index 294271e414d94..9bf50ce8bf98c 100644 --- a/libs/langchain/poetry.lock +++ b/libs/langchain/poetry.lock @@ -3049,6 +3049,7 @@ files = [ {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:227b178b22a7f91ae88525810441791b1ca1fc71c86f03190911793be15cec3d"}, {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:780eb6383fbae12afa819ef676fc93e1548ae4b076c004a393af26a04b460742"}, {file = "jq-1.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08ded6467f4ef89fec35b2bf310f210f8cd13fbd9d80e521500889edf8d22441"}, + {file = "jq-1.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49e44ed677713f4115bd5bf2dbae23baa4cd503be350e12a1c1f506b0687848f"}, {file = "jq-1.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:984f33862af285ad3e41e23179ac4795f1701822473e1a26bf87ff023e5a89ea"}, {file = "jq-1.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42264fafc6166efb5611b5d4cb01058887d050a6c19334f6a3f8a13bb369df5"}, {file = "jq-1.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a67154f150aaf76cc1294032ed588436eb002097dd4fd1e283824bf753a05080"}, @@ -3446,7 +3447,7 @@ files = [ [[package]] name = "langchain-community" -version = "0.0.13" +version = "0.0.14" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.8.1,<4.0" @@ -3456,7 +3457,7 @@ develop = true [package.dependencies] aiohttp = "^3.8.3" dataclasses-json = ">= 0.5.7, < 0.7" -langchain-core = ">=0.1.9,<0.2" +langchain-core = ">=0.1.14,<0.2" langsmith = "~0.0.63" numpy = "^1" PyYAML = ">=5.3" @@ -3474,7 +3475,7 @@ url = "../community" [[package]] name = "langchain-core" -version = "0.1.11" +version = "0.1.14" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -3484,7 +3485,7 @@ develop = true [package.dependencies] anyio = ">=3,<5" jsonpatch = "^1.33" -langsmith = "~0.0.63" +langsmith = "^0.0.83" packaging = "^23.2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -3743,6 +3744,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -5796,7 +5807,6 @@ files = [ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96"}, {file = "pymongo-4.5.0-cp312-cp312-win32.whl", hash = "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60"}, {file = "pymongo-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807"}, - {file = "pymongo-4.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8443f3a8ab2d929efa761c6ebce39a6c1dca1c9ac186ebf11b62c8fe1aef53f4"}, {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a"}, {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257"}, {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93"}, @@ -9118,4 +9128,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "395f3b3b3c486be29ab663cdb12ce577c231027b32671c4f48848944883db9f8" +content-hash = "9eb6114c56ea7772809c5cddd72986099861c63903eaed1649b205dbb662fa09" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index 1547b98c5ea46..455b0fbbfc4c9 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain" -version = "0.1.1" +version = "0.1.2" description = "Building applications with LLMs through composability" authors = [] license = "MIT" @@ -12,8 +12,8 @@ langchain-server = "langchain.server:main" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" -langchain-core = ">=0.1.9,<0.2" -langchain-community = ">=0.0.13,<0.1" +langchain-core = ">=0.1.14,<0.2" +langchain-community = ">=0.0.14,<0.1" pydantic = ">=1,<3" SQLAlchemy = ">=1.4,<3" requests = "^2" From 1445ac95e828de321785abc42bc3ef23ae2eef46 Mon Sep 17 00:00:00 2001 From: Tom Jorquera <tomjorquera@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:54:18 +0100 Subject: [PATCH 140/215] community[patch]: Enable streaming for GPT4all (#16392) `streaming` param was never passed to model --- libs/community/langchain_community/llms/gpt4all.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/community/langchain_community/llms/gpt4all.py b/libs/community/langchain_community/llms/gpt4all.py index 83dace226bb29..8b347ceb5c38b 100644 --- a/libs/community/langchain_community/llms/gpt4all.py +++ b/libs/community/langchain_community/llms/gpt4all.py @@ -111,6 +111,7 @@ def _model_param_names() -> Set[str]: "n_batch", "repeat_penalty", "repeat_last_n", + "streaming", } def _default_params(self) -> Dict[str, Any]: @@ -123,6 +124,7 @@ def _default_params(self) -> Dict[str, Any]: "n_batch": self.n_batch, "repeat_penalty": self.repeat_penalty, "repeat_last_n": self.repeat_last_n, + "streaming": self.streaming, } @root_validator() From 54f90fc6bc8f33609bb1896d6e4332324731d438 Mon Sep 17 00:00:00 2001 From: y2noda <42732270+y2noda@users.noreply.github.com> Date: Tue, 23 Jan 2024 03:16:36 +0900 Subject: [PATCH 141/215] langchain_google_vertexai:Enable the use of langchain's built-in tools in Gemini's function calling (#16341) - **Issue:** This is a PR about #16340 <!-- Thank you for contributing to LangChain! Please title your PR "<package>: <description>", where <package> is whichever of langchain, community, core, experimental, etc. is being modified. Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes if applicable, - **Dependencies:** any dependencies required for this change, - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` from the root of the package you've modified to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ 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. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> Co-authored-by: yuhei.tsunoda <yuhei.tsunoda@brainpad.co.jp> --- .../langchain_google_vertexai/functions_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py index d1786ae67865e..b44b679afcbcf 100644 --- a/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py +++ b/libs/partners/google-vertexai/langchain_google_vertexai/functions_utils.py @@ -5,7 +5,7 @@ from langchain_core.output_parsers import BaseOutputParser from langchain_core.outputs import ChatGeneration, Generation from langchain_core.pydantic_v1 import BaseModel -from langchain_core.tools import Tool +from langchain_core.tools import BaseTool from langchain_core.utils.function_calling import FunctionDescription from langchain_core.utils.json_schema import dereference_refs from vertexai.preview.generative_models import ( # type: ignore @@ -39,7 +39,7 @@ def _format_pydantic_to_vertex_function( } -def _format_tool_to_vertex_function(tool: Tool) -> FunctionDescription: +def _format_tool_to_vertex_function(tool: BaseTool) -> FunctionDescription: "Format tool into the Vertex function API." if tool.args_schema: schema = dereference_refs(tool.args_schema.schema()) @@ -75,12 +75,12 @@ def _format_tool_to_vertex_function(tool: Tool) -> FunctionDescription: def _format_tools_to_vertex_tool( - tools: List[Union[Tool, Type[BaseModel]]], + tools: List[Union[BaseTool, Type[BaseModel]]], ) -> List[VertexTool]: "Format tool into the Vertex Tool instance." function_declarations = [] for tool in tools: - if isinstance(tool, Tool): + if isinstance(tool, BaseTool): func = _format_tool_to_vertex_function(tool) else: func = _format_pydantic_to_vertex_function(tool) From de209af533ae9cfd761ccfe27e23fdff3caef1af Mon Sep 17 00:00:00 2001 From: Max Jakob <max.jakob@elastic.co> Date: Mon, 22 Jan 2024 19:52:20 +0100 Subject: [PATCH 142/215] community[patch]: ElasticsearchStore: add relevance function selector (#16378) Implement similarity function selector for ElasticsearchStore. The scores coming back from Elasticsearch are already similarities (not distances) and they are already normalized (see [docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params)). Hence we leave the scores untouched and just forward them. This fixes #11539. However, in hybrid mode (when keyword search and vector search are involved) Elasticsearch currently returns no scores. This PR adds an error message around this fact. We need to think a bit more to come up with a solution for this case. This PR also corrects a small error in the Elasticsearch integration test. --------- Co-authored-by: Erick Friis <erick@langchain.dev> --- .../vectorstores/elasticsearch.py | 26 ++++++- libs/community/poetry.lock | 68 +++++++++++-------- libs/community/pyproject.toml | 2 + .../vectorstores/test_elasticsearch.py | 42 +++++++++++- .../vectorstores/test_elasticsearch.py | 32 +++++++++ 5 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 libs/community/tests/unit_tests/vectorstores/test_elasticsearch.py diff --git a/libs/community/langchain_community/vectorstores/elasticsearch.py b/libs/community/langchain_community/vectorstores/elasticsearch.py index 24c91472c9f62..7664ae24febf6 100644 --- a/libs/community/langchain_community/vectorstores/elasticsearch.py +++ b/libs/community/langchain_community/vectorstores/elasticsearch.py @@ -388,7 +388,6 @@ class ElasticsearchStore(VectorStore): from langchain_community.vectorstores import ElasticsearchStore from langchain_community.embeddings.openai import OpenAIEmbeddings - embeddings = OpenAIEmbeddings() vectorstore = ElasticsearchStore( embedding=OpenAIEmbeddings(), index_name="langchain-demo", @@ -693,6 +692,25 @@ def max_marginal_relevance_search( return selected_docs + @staticmethod + def _identity_fn(score: float) -> float: + return score + + def _select_relevance_score_fn(self) -> Callable[[float], float]: + """ + The 'correct' relevance function + may differ depending on a few things, including: + - the distance / similarity metric used by the VectorStore + - the scale of your embeddings (OpenAI's are unit normed. Many others are not!) + - embedding dimensionality + - etc. + + Vectorstores should define their own selection based method of relevance. + """ + # All scores from Elasticsearch are already normalized similarities: + # https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params + return self._identity_fn + def similarity_search_with_score( self, query: str, k: int = 4, filter: Optional[List[dict]] = None, **kwargs: Any ) -> List[Tuple[Document, float]]: @@ -706,6 +724,9 @@ def similarity_search_with_score( Returns: List of Documents most similar to the query and score for each """ + if isinstance(self.strategy, ApproxRetrievalStrategy) and self.strategy.hybrid: + raise ValueError("scores are currently not supported in hybrid mode") + return self._search(query=query, k=k, filter=filter, **kwargs) def similarity_search_by_vector_with_relevance_scores( @@ -725,6 +746,9 @@ def similarity_search_by_vector_with_relevance_scores( Returns: List of Documents most similar to the embedding and score for each """ + if isinstance(self.strategy, ApproxRetrievalStrategy) and self.strategy.hybrid: + raise ValueError("scores are currently not supported in hybrid mode") + return self._search(query_vector=embedding, k=k, filter=filter, **kwargs) def _search( diff --git a/libs/community/poetry.lock b/libs/community/poetry.lock index 9254690bb093f..3fdb4118bf872 100644 --- a/libs/community/poetry.lock +++ b/libs/community/poetry.lock @@ -1173,7 +1173,6 @@ files = [ {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, - {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, @@ -1182,7 +1181,6 @@ files = [ {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, - {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, @@ -1191,7 +1189,6 @@ files = [ {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, - {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, @@ -1200,7 +1197,6 @@ files = [ {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, - {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, @@ -1769,6 +1765,42 @@ files = [ duckdb = ">=0.4.0" sqlalchemy = ">=1.3.22" +[[package]] +name = "elastic-transport" +version = "8.12.0" +description = "Transport classes and utilities shared among Python Elastic client libraries" +optional = true +python-versions = ">=3.7" +files = [ + {file = "elastic-transport-8.12.0.tar.gz", hash = "sha256:48839b942fcce199eece1558ecea6272e116c58da87ca8d495ef12eb61effaf7"}, + {file = "elastic_transport-8.12.0-py3-none-any.whl", hash = "sha256:87d9dc9dee64a05235e7624ed7e6ab6e5ca16619aa7a6d22e853273b9f1cfbee"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.2,<3" + +[package.extras] +develop = ["aiohttp", "furo", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "sphinx (>2)", "sphinx-autodoc-typehints", "trustme"] + +[[package]] +name = "elasticsearch" +version = "8.12.0" +description = "Python client for Elasticsearch" +optional = true +python-versions = ">=3.7" +files = [ + {file = "elasticsearch-8.12.0-py3-none-any.whl", hash = "sha256:d394c5ce746bb8cb97827feae57759dae462bce34df221a6fdb6875c56476389"}, + {file = "elasticsearch-8.12.0.tar.gz", hash = "sha256:58fd3876682f7529c33b9eeee701e71cfcc334bb45d725e315e22a0a5e2611fb"}, +] + +[package.dependencies] +elastic-transport = ">=8,<9" + +[package.extras] +async = ["aiohttp (>=3,<4)"] +requests = ["requests (>=2.4.0,<3.0.0)"] + [[package]] name = "entrypoints" version = "0.4" @@ -4132,16 +4164,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -6701,7 +6723,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -6709,15 +6730,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -6734,7 +6748,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -6742,7 +6755,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -7714,9 +7726,7 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"}, - {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"}, - {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"}, {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"}, @@ -7753,9 +7763,7 @@ files = [ {file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"}, - {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"}, - {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"}, {file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"}, @@ -9167,9 +9175,9 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [extras] cli = ["typer"] -extended-testing = ["aiosqlite", "aleph-alpha-client", "anthropic", "arxiv", "assemblyai", "atlassian-python-api", "azure-ai-documentintelligence", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "cohere", "dashvector", "databricks-vectorsearch", "datasets", "dgml-utils", "esprima", "faiss-cpu", "feedparser", "fireworks-ai", "geopandas", "gitpython", "google-cloud-documentai", "gql", "gradientai", "hologres-vector", "html2text", "javelin-sdk", "jinja2", "jq", "jsonschema", "lxml", "markdownify", "motor", "msal", "mwparserfromhell", "mwxml", "newspaper3k", "numexpr", "openai", "openapi-pydantic", "oracle-ads", "pandas", "pdfminer-six", "pgvector", "praw", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "rapidocr-onnxruntime", "requests-toolbelt", "rspace_client", "scikit-learn", "sqlite-vss", "streamlit", "sympy", "telethon", "timescale-vector", "tqdm", "upstash-redis", "xata", "xmltodict", "zhipuai"] +extended-testing = ["aiosqlite", "aleph-alpha-client", "anthropic", "arxiv", "assemblyai", "atlassian-python-api", "azure-ai-documentintelligence", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "cohere", "dashvector", "databricks-vectorsearch", "datasets", "dgml-utils", "elasticsearch", "esprima", "faiss-cpu", "feedparser", "fireworks-ai", "geopandas", "gitpython", "google-cloud-documentai", "gql", "gradientai", "hologres-vector", "html2text", "javelin-sdk", "jinja2", "jq", "jsonschema", "lxml", "markdownify", "motor", "msal", "mwparserfromhell", "mwxml", "newspaper3k", "numexpr", "openai", "openapi-pydantic", "oracle-ads", "pandas", "pdfminer-six", "pgvector", "praw", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "rapidocr-onnxruntime", "requests-toolbelt", "rspace_client", "scikit-learn", "sqlite-vss", "streamlit", "sympy", "telethon", "timescale-vector", "tqdm", "upstash-redis", "xata", "xmltodict", "zhipuai"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "3b7748a2cbf7b875397483cfdee5e695f447880dffc6c90c67d92de9cc04a7bb" +content-hash = "e512944a95e344bcf8b15066e289798c53fb39299f6e0190bf69371f43e6f63a" diff --git a/libs/community/pyproject.toml b/libs/community/pyproject.toml index bb9b97edb1e7b..8528edbdb3ad9 100644 --- a/libs/community/pyproject.toml +++ b/libs/community/pyproject.toml @@ -87,6 +87,7 @@ datasets = {version = "^2.15.0", optional = true} azure-ai-documentintelligence = {version = "^1.0.0b1", optional = true} oracle-ads = {version = "^2.9.1", optional = true} zhipuai = {version = "^1.0.7", optional = true} +elasticsearch = {version = "^8.12.0", optional = true} [tool.poetry.group.test] optional = true @@ -249,6 +250,7 @@ extended_testing = [ "azure-ai-documentintelligence", "oracle-ads", "zhipuai", + "elasticsearch", ] [tool.ruff] diff --git a/libs/community/tests/integration_tests/vectorstores/test_elasticsearch.py b/libs/community/tests/integration_tests/vectorstores/test_elasticsearch.py index 8d99fbe5ead77..ff13a96dc8a15 100644 --- a/libs/community/tests/integration_tests/vectorstores/test_elasticsearch.py +++ b/libs/community/tests/integration_tests/vectorstores/test_elasticsearch.py @@ -157,7 +157,7 @@ def assert_query(query_body: dict, query: str) -> dict: output = docsearch.similarity_search("foo", k=1, custom_query=assert_query) assert output == [Document(page_content="foo")] - async def test_similarity_search_without_metadat_async( + async def test_similarity_search_without_metadata_async( self, elasticsearch_connection: dict, index_name: str ) -> None: """Test end to end construction and search without metadata.""" @@ -400,7 +400,7 @@ def assert_query(query_body: dict, query: str) -> dict: "script": { "source": """ double value = dotProduct(params.query_vector, 'vector'); - return sigmoid(1, Math.E, -value); + return sigmoid(1, Math.E, -value); """, "params": { "query_vector": [ @@ -777,6 +777,44 @@ def test_elasticsearch_with_relevance_score( ) assert output == [(Document(page_content="foo", metadata={"page": "0"}), 1.0)] + def test_elasticsearch_with_relevance_threshold( + self, elasticsearch_connection: dict, index_name: str + ) -> None: + """Test to make sure the relevance threshold is respected.""" + texts = ["foo", "bar", "baz"] + metadatas = [{"page": str(i)} for i in range(len(texts))] + embeddings = FakeEmbeddings() + + docsearch = ElasticsearchStore.from_texts( + index_name=index_name, + texts=texts, + embedding=embeddings, + metadatas=metadatas, + **elasticsearch_connection, + ) + + # Find a good threshold for testing + query_string = "foo" + embedded_query = embeddings.embed_query(query_string) + top3 = docsearch.similarity_search_by_vector_with_relevance_scores( + embedding=embedded_query, k=3 + ) + similarity_of_second_ranked = top3[1][1] + assert len(top3) == 3 + + # Test threshold + retriever = docsearch.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={"score_threshold": similarity_of_second_ranked}, + ) + output = retriever.get_relevant_documents(query=query_string) + + assert output == [ + top3[0][0], + top3[1][0], + # third ranked is out + ] + def test_elasticsearch_delete_ids( self, elasticsearch_connection: dict, index_name: str ) -> None: diff --git a/libs/community/tests/unit_tests/vectorstores/test_elasticsearch.py b/libs/community/tests/unit_tests/vectorstores/test_elasticsearch.py new file mode 100644 index 0000000000000..4a6b5a4dab7bf --- /dev/null +++ b/libs/community/tests/unit_tests/vectorstores/test_elasticsearch.py @@ -0,0 +1,32 @@ +"""Test Elasticsearch functionality.""" +import pytest + +from langchain_community.vectorstores.elasticsearch import ( + ApproxRetrievalStrategy, + ElasticsearchStore, +) +from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings + + +@pytest.mark.requires("elasticsearch") +def test_elasticsearch_hybrid_scores_guard() -> None: + """Ensure an error is raised when search with score in hybrid mode + because in this case Elasticsearch does not return any score. + """ + from elasticsearch import Elasticsearch + + query_string = "foo" + embeddings = FakeEmbeddings() + + store = ElasticsearchStore( + index_name="dummy_index", + es_connection=Elasticsearch(hosts=["http://dummy-host:9200"]), + embedding=embeddings, + strategy=ApproxRetrievalStrategy(hybrid=True), + ) + with pytest.raises(ValueError): + store.similarity_search_with_score(query_string) + + embedded_query = embeddings.embed_query(query_string) + with pytest.raises(ValueError): + store.similarity_search_by_vector_with_relevance_scores(embedded_query) From 85e8423312f92f671b7b35ae90103cc324895831 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:11:03 -0800 Subject: [PATCH 143/215] community[patch]: Update bing results tool name (#16395) Make BingSearchResults tool name OpenAI functions compatible (can't have spaces). Fixes #16368 --- libs/community/langchain_community/tools/bing_search/tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/tools/bing_search/tool.py b/libs/community/langchain_community/tools/bing_search/tool.py index 027f2750c9418..658b5011ca561 100644 --- a/libs/community/langchain_community/tools/bing_search/tool.py +++ b/libs/community/langchain_community/tools/bing_search/tool.py @@ -31,7 +31,7 @@ def _run( class BingSearchResults(BaseTool): """Tool that queries the Bing Search API and gets back json.""" - name: str = "Bing Search Results JSON" + name: str = "bing_search_results_json" description: str = ( "A wrapper around Bing Search. " "Useful for when you need to answer questions about current events. " From eac91b60c90571c82a43843a6d709669a30a09dd Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:17:32 -0800 Subject: [PATCH 144/215] docs: qa rag nit (#16400) --- .../docs/use_cases/question_answering/chat_history.ipynb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/docs/use_cases/question_answering/chat_history.ipynb b/docs/docs/use_cases/question_answering/chat_history.ipynb index ffae5465de0e7..0478fb846cb74 100644 --- a/docs/docs/use_cases/question_answering/chat_history.ipynb +++ b/docs/docs/use_cases/question_answering/chat_history.ipynb @@ -359,11 +359,20 @@ "Here we've gone over how to add application logic for incorporating historical outputs, but we're still manually updating the chat history and inserting it into each input. In a real Q&A application we'll want some way of persisting chat history and some way of automatically inserting and updating it.\n", "\n", "For this we can use:\n", + "\n", "- [BaseChatMessageHistory](/docs/modules/memory/chat_messages/): Store chat history.\n", "- [RunnableWithMessageHistory](/docs/expression_language/how_to/message_history): Wrapper for an LCEL chain and a `BaseChatMessageHistory` that handles injecting chat history into inputs and updating it after each invocation.\n", "\n", "For a detailed walkthrough of how to use these classes together to create a stateful conversational chain, head to the [How to add message history (memory)](/docs/expression_language/how_to/message_history) LCEL page." ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f67a60a-0a31-4315-9cce-19c78d658f6a", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From fc196cab12e214ff447be1ceb6d6b108916b4db2 Mon Sep 17 00:00:00 2001 From: Iskren Ivov Chernev <me@iskren.info> Date: Mon, 22 Jan 2024 21:22:17 +0200 Subject: [PATCH 145/215] community[minor]: DeepInfra support for chat models (#16380) Add deepinfra chat models support. This is https://github.com/langchain-ai/langchain/pull/14234 re-opened from my branch (so maintainers can edit). --- docs/docs/integrations/chat/deepinfra.ipynb | 224 +++++++++ .../docs/integrations/providers/deepinfra.mdx | 10 + .../chat_models/__init__.py | 2 + .../chat_models/deepinfra.py | 451 ++++++++++++++++++ .../chat_models/test_deepinfra.py | 65 +++ .../embeddings/test_deepinfra.py | 8 +- .../integration_tests/llms/test_deepinfra.py | 4 +- .../unit_tests/chat_models/test_imports.py | 1 + 8 files changed, 759 insertions(+), 6 deletions(-) create mode 100644 docs/docs/integrations/chat/deepinfra.ipynb create mode 100644 libs/community/langchain_community/chat_models/deepinfra.py create mode 100644 libs/community/tests/integration_tests/chat_models/test_deepinfra.py diff --git a/docs/docs/integrations/chat/deepinfra.ipynb b/docs/docs/integrations/chat/deepinfra.ipynb new file mode 100644 index 0000000000000..0f88097a20ac7 --- /dev/null +++ b/docs/docs/integrations/chat/deepinfra.ipynb @@ -0,0 +1,224 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "bf733a38-db84-4363-89e2-de6735c37230", + "metadata": {}, + "source": [ + "# DeepInfra\n", + "\n", + "[DeepInfra](https://deepinfra.com/?utm_source=langchain) is a serverless inference as a service that provides access to a [variety of LLMs](https://deepinfra.com/models?utm_source=langchain) and [embeddings models](https://deepinfra.com/models?type=embeddings&utm_source=langchain). This notebook goes over how to use LangChain with DeepInfra for chat models." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set the Environment API Key\n", + "Make sure to get your API key from DeepInfra. You have to [Login](https://deepinfra.com/login?from=%2Fdash) and get a new token.\n", + "\n", + "You are given a 1 hour free of serverless GPU compute to test different models. (see [here](https://github.com/deepinfra/deepctl#deepctl))\n", + "You can print your token with `deepctl auth token`" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ········\n" + ] + } + ], + "source": [ + "# get a new token: https://deepinfra.com/login?from=%2Fdash\n", + "\n", + "from getpass import getpass\n", + "\n", + "DEEPINFRA_API_TOKEN = getpass()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# or pass deepinfra_api_token parameter to the ChatDeepInfra constructor\n", + "os.environ[\"DEEPINFRA_API_TOKEN\"] = DEEPINFRA_API_TOKEN" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d4a7c55d-b235-4ca4-a579-c90cc9570da9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain.chat_models import ChatDeepInfra\n", + "from langchain.schema import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "70cf04e8-423a-4ff6-8b09-f11fb711c817", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "chat = ChatDeepInfra(model=\"meta-llama/Llama-2-7b-chat-hf\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8199ef8f-eb8b-4253-9ea0-6c24a013ca4c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=\" J'aime la programmation.\", additional_kwargs={}, example=False)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "messages = [\n", + " HumanMessage(\n", + " content=\"Translate this sentence from English to French. I love programming.\"\n", + " )\n", + "]\n", + "chat(messages)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c361ab1e-8c0c-4206-9e3c-9d1424a12b9c", + "metadata": {}, + "source": [ + "## `ChatDeepInfra` also supports async and streaming functionality:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "93a21c5c-6ef9-4688-be60-b2e1f94842fb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c5fac0e9-05a4-4fc1-a3b3-e5bbb24b971b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "LLMResult(generations=[[ChatGeneration(text=\" J'aime programmer.\", generation_info=None, message=AIMessage(content=\" J'aime programmer.\", additional_kwargs={}, example=False))]], llm_output={}, run=[RunInfo(run_id=UUID('8cc8fb68-1c35-439c-96a0-695036a93652'))])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await chat.agenerate([messages])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "025be980-e50d-4a68-93dc-c9c7b500ce34", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " J'aime la programmation." + ] + }, + { + "data": { + "text/plain": [ + "AIMessage(content=\" J'aime la programmation.\", additional_kwargs={}, example=False)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chat = ChatDeepInfra(\n", + " streaming=True,\n", + " verbose=True,\n", + " callbacks=[StreamingStdOutCallbackHandler()],\n", + ")\n", + "chat(messages)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c253883f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/integrations/providers/deepinfra.mdx b/docs/docs/integrations/providers/deepinfra.mdx index d370862aa65bd..06af21f287ee8 100644 --- a/docs/docs/integrations/providers/deepinfra.mdx +++ b/docs/docs/integrations/providers/deepinfra.mdx @@ -17,6 +17,8 @@ google/flan\* models can be viewed [here](https://deepinfra.com/models?type=text You can view a [list of request and response parameters](https://deepinfra.com/meta-llama/Llama-2-70b-chat-hf/api). +Chat models [follow openai api](https://deepinfra.com/meta-llama/Llama-2-70b-chat-hf/api?example=openai-http) + ## Wrappers ### LLM @@ -34,3 +36,11 @@ There is also an DeepInfra Embeddings wrapper, you can access with ```python from langchain_community.embeddings import DeepInfraEmbeddings ``` + +### Chat Models + +There is a chat-oriented wrapper as well, accessible with + +```python +from langchain_community.chat_models import ChatDeepInfra +``` diff --git a/libs/community/langchain_community/chat_models/__init__.py b/libs/community/langchain_community/chat_models/__init__.py index aec1fcb8784b4..067a57038ba13 100644 --- a/libs/community/langchain_community/chat_models/__init__.py +++ b/libs/community/langchain_community/chat_models/__init__.py @@ -25,6 +25,7 @@ from langchain_community.chat_models.bedrock import BedrockChat from langchain_community.chat_models.cohere import ChatCohere from langchain_community.chat_models.databricks import ChatDatabricks +from langchain_community.chat_models.deepinfra import ChatDeepInfra from langchain_community.chat_models.ernie import ErnieBotChat from langchain_community.chat_models.everlyai import ChatEverlyAI from langchain_community.chat_models.fake import FakeListChatModel @@ -61,6 +62,7 @@ "FakeListChatModel", "PromptLayerChatOpenAI", "ChatDatabricks", + "ChatDeepInfra", "ChatEverlyAI", "ChatAnthropic", "ChatCohere", diff --git a/libs/community/langchain_community/chat_models/deepinfra.py b/libs/community/langchain_community/chat_models/deepinfra.py new file mode 100644 index 0000000000000..7b8a40d3c119f --- /dev/null +++ b/libs/community/langchain_community/chat_models/deepinfra.py @@ -0,0 +1,451 @@ +"""deepinfra.com chat models wrapper""" +from __future__ import annotations + +import json +import logging +from typing import ( + Any, + AsyncIterator, + Callable, + Dict, + Iterator, + List, + Mapping, + Optional, + Tuple, + Type, + Union, +) + +import aiohttp +import requests +from langchain_core.callbacks.manager import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain_core.language_models.chat_models import ( + BaseChatModel, + agenerate_from_stream, + generate_from_stream, +) +from langchain_core.language_models.llms import create_base_retry_decorator +from langchain_core.messages import ( + AIMessage, + AIMessageChunk, + BaseMessage, + BaseMessageChunk, + ChatMessage, + ChatMessageChunk, + FunctionMessage, + FunctionMessageChunk, + HumanMessage, + HumanMessageChunk, + SystemMessage, + SystemMessageChunk, +) +from langchain_core.outputs import ( + ChatGeneration, + ChatGenerationChunk, + ChatResult, +) +from langchain_core.pydantic_v1 import Field, root_validator +from langchain_core.utils import get_from_dict_or_env + +# from langchain.llms.base import create_base_retry_decorator +from langchain_community.utilities.requests import Requests + +logger = logging.getLogger(__name__) + + +class ChatDeepInfraException(Exception): + pass + + +def _create_retry_decorator( + llm: ChatDeepInfra, + run_manager: Optional[ + Union[AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun] + ] = None, +) -> Callable[[Any], Any]: + """Returns a tenacity retry decorator, preconfigured to handle PaLM exceptions""" + return create_base_retry_decorator( + error_types=[requests.exceptions.ConnectTimeout, ChatDeepInfraException], + max_retries=llm.max_retries, + run_manager=run_manager, + ) + + +def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage: + role = _dict["role"] + if role == "user": + return HumanMessage(content=_dict["content"]) + elif role == "assistant": + # Fix for azure + # Also OpenAI returns None for tool invocations + content = _dict.get("content", "") or "" + if _dict.get("function_call"): + additional_kwargs = {"function_call": dict(_dict["function_call"])} + else: + additional_kwargs = {} + return AIMessage(content=content, additional_kwargs=additional_kwargs) + elif role == "system": + return SystemMessage(content=_dict["content"]) + elif role == "function": + return FunctionMessage(content=_dict["content"], name=_dict["name"]) + else: + return ChatMessage(content=_dict["content"], role=role) + + +def _convert_delta_to_message_chunk( + _dict: Mapping[str, Any], default_class: Type[BaseMessageChunk] +) -> BaseMessageChunk: + role = _dict.get("role") + content = _dict.get("content") or "" + if _dict.get("function_call"): + additional_kwargs = {"function_call": dict(_dict["function_call"])} + else: + additional_kwargs = {} + + if role == "user" or default_class == HumanMessageChunk: + return HumanMessageChunk(content=content) + elif role == "assistant" or default_class == AIMessageChunk: + return AIMessageChunk(content=content, additional_kwargs=additional_kwargs) + elif role == "system" or default_class == SystemMessageChunk: + return SystemMessageChunk(content=content) + elif role == "function" or default_class == FunctionMessageChunk: + return FunctionMessageChunk(content=content, name=_dict["name"]) + elif role or default_class == ChatMessageChunk: + return ChatMessageChunk(content=content, role=role) + else: + return default_class(content=content) + + +def _convert_message_to_dict(message: BaseMessage) -> dict: + if isinstance(message, ChatMessage): + message_dict = {"role": message.role, "content": message.content} + elif isinstance(message, HumanMessage): + message_dict = {"role": "user", "content": message.content} + elif isinstance(message, AIMessage): + message_dict = {"role": "assistant", "content": message.content} + if "function_call" in message.additional_kwargs: + message_dict["function_call"] = message.additional_kwargs["function_call"] + elif isinstance(message, SystemMessage): + message_dict = {"role": "system", "content": message.content} + elif isinstance(message, FunctionMessage): + message_dict = { + "role": "function", + "content": message.content, + "name": message.name, + } + else: + raise ValueError(f"Got unknown type {message}") + if "name" in message.additional_kwargs: + message_dict["name"] = message.additional_kwargs["name"] + return message_dict + + +class ChatDeepInfra(BaseChatModel): + """A chat model that uses the DeepInfra API.""" + + # client: Any #: :meta private: + model_name: str = Field(default="meta-llama/Llama-2-70b-chat-hf", alias="model") + """Model name to use.""" + deepinfra_api_token: Optional[str] = None + request_timeout: Optional[float] = Field(default=None, alias="timeout") + temperature: Optional[float] = 1 + model_kwargs: Dict[str, Any] = Field(default_factory=dict) + """Run inference with this temperature. Must by in the closed + interval [0.0, 1.0].""" + top_p: Optional[float] = None + """Decode using nucleus sampling: consider the smallest set of tokens whose + probability sum is at least top_p. Must be in the closed interval [0.0, 1.0].""" + top_k: Optional[int] = None + """Decode using top-k sampling: consider the set of top_k most probable tokens. + Must be positive.""" + n: int = 1 + """Number of chat completions to generate for each prompt. Note that the API may + not return the full n completions if duplicates are generated.""" + max_tokens: int = 256 + streaming: bool = False + max_retries: int = 1 + + @property + def _default_params(self) -> Dict[str, Any]: + """Get the default parameters for calling OpenAI API.""" + return { + "model": self.model_name, + "max_tokens": self.max_tokens, + "stream": self.streaming, + "n": self.n, + "temperature": self.temperature, + "request_timeout": self.request_timeout, + **self.model_kwargs, + } + + @property + def _client_params(self) -> Dict[str, Any]: + """Get the parameters used for the openai client.""" + return {**self._default_params} + + def completion_with_retry( + self, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any + ) -> Any: + """Use tenacity to retry the completion call.""" + retry_decorator = _create_retry_decorator(self, run_manager=run_manager) + + @retry_decorator + def _completion_with_retry(**kwargs: Any) -> Any: + try: + request_timeout = kwargs.pop("request_timeout") + request = Requests(headers=self._headers()) + response = request.post( + url=self._url(), data=self._body(kwargs), timeout=request_timeout + ) + self._handle_status(response.status_code, response.text) + return response + except Exception as e: + # import pdb; pdb.set_trace() + print("EX", e) + raise + + return _completion_with_retry(**kwargs) + + async def acompletion_with_retry( + self, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Any: + """Use tenacity to retry the async completion call.""" + retry_decorator = _create_retry_decorator(self, run_manager=run_manager) + + @retry_decorator + async def _completion_with_retry(**kwargs: Any) -> Any: + try: + request_timeout = kwargs.pop("request_timeout") + request = Requests(headers=self._headers()) + async with request.apost( + url=self._url(), data=self._body(kwargs), timeout=request_timeout + ) as response: + self._handle_status(response.status, response.text) + return await response.json() + except Exception as e: + print("EX", e) + raise + + return await _completion_with_retry(**kwargs) + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + """Validate api key, python package exists, temperature, top_p, and top_k.""" + # For compatibility with LiteLLM + api_key = get_from_dict_or_env( + values, + "deepinfra_api_key", + "DEEPINFRA_API_KEY", + default="", + ) + values["deepinfra_api_token"] = get_from_dict_or_env( + values, + "deepinfra_api_token", + "DEEPINFRA_API_TOKEN", + default=api_key, + ) + + if values["temperature"] is not None and not 0 <= values["temperature"] <= 1: + raise ValueError("temperature must be in the range [0.0, 1.0]") + + if values["top_p"] is not None and not 0 <= values["top_p"] <= 1: + raise ValueError("top_p must be in the range [0.0, 1.0]") + + if values["top_k"] is not None and values["top_k"] <= 0: + raise ValueError("top_k must be positive") + + return values + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + stream: Optional[bool] = None, + **kwargs: Any, + ) -> ChatResult: + should_stream = stream if stream is not None else self.streaming + if should_stream: + stream_iter = self._stream( + messages, stop=stop, run_manager=run_manager, **kwargs + ) + return generate_from_stream(stream_iter) + + message_dicts, params = self._create_message_dicts(messages, stop) + params = {**params, **kwargs} + response = self.completion_with_retry( + messages=message_dicts, run_manager=run_manager, **params + ) + return self._create_chat_result(response.json()) + + def _create_chat_result(self, response: Mapping[str, Any]) -> ChatResult: + generations = [] + for res in response["choices"]: + message = _convert_dict_to_message(res["message"]) + gen = ChatGeneration( + message=message, + generation_info=dict(finish_reason=res.get("finish_reason")), + ) + generations.append(gen) + token_usage = response.get("usage", {}) + llm_output = {"token_usage": token_usage, "model": self.model_name} + res = ChatResult(generations=generations, llm_output=llm_output) + return res + + def _create_message_dicts( + self, messages: List[BaseMessage], stop: Optional[List[str]] + ) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: + params = self._client_params + if stop is not None: + if "stop" in params: + raise ValueError("`stop` found in both the input and default params.") + params["stop"] = stop + message_dicts = [_convert_message_to_dict(m) for m in messages] + return message_dicts, params + + def _stream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[ChatGenerationChunk]: + message_dicts, params = self._create_message_dicts(messages, stop) + params = {**params, **kwargs, "stream": True} + + response = self.completion_with_retry( + messages=message_dicts, run_manager=run_manager, **params + ) + for line in _parse_stream(response.iter_lines()): + chunk = _handle_sse_line(line) + if chunk: + yield ChatGenerationChunk(message=chunk, generation_info=None) + if run_manager: + run_manager.on_llm_new_token(chunk.content) # type: ignore[arg-type] + + async def _astream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> AsyncIterator[ChatGenerationChunk]: + message_dicts, params = self._create_message_dicts(messages, stop) + params = {"messages": message_dicts, "stream": True, **params, **kwargs} + + request_timeout = params.pop("request_timeout") + request = Requests(headers=self._headers()) + async with request.apost( + url=self._url(), data=self._body(params), timeout=request_timeout + ) as response: + async for line in _parse_stream_async(response.content): + chunk = _handle_sse_line(line) + if chunk: + yield ChatGenerationChunk(message=chunk, generation_info=None) + if run_manager: + await run_manager.on_llm_new_token(chunk.content) # type: ignore[arg-type] + + async def _agenerate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + stream: Optional[bool] = None, + **kwargs: Any, + ) -> ChatResult: + should_stream = stream if stream is not None else self.streaming + if should_stream: + stream_iter = self._astream( + messages, stop=stop, run_manager=run_manager, **kwargs + ) + return await agenerate_from_stream(stream_iter) + + message_dicts, params = self._create_message_dicts(messages, stop) + params = {"messages": message_dicts, **params, **kwargs} + + res = await self.acompletion_with_retry(run_manager=run_manager, **params) + return self._create_chat_result(res) + + @property + def _identifying_params(self) -> Dict[str, Any]: + """Get the identifying parameters.""" + return { + "model": self.model_name, + "temperature": self.temperature, + "top_p": self.top_p, + "top_k": self.top_k, + "n": self.n, + } + + @property + def _llm_type(self) -> str: + return "deepinfra-chat" + + def _handle_status(self, code: int, text: Any) -> None: + if code >= 500: + raise ChatDeepInfraException(f"DeepInfra Server: Error {code}") + elif code >= 400: + raise ValueError(f"DeepInfra received an invalid payload: {text}") + elif code != 200: + raise Exception( + f"DeepInfra returned an unexpected response with status " + f"{code}: {text}" + ) + + def _url(self) -> str: + return "https://stage.api.deepinfra.com/v1/openai/chat/completions" + + def _headers(self) -> Dict: + return { + "Authorization": f"bearer {self.deepinfra_api_token}", + "Content-Type": "application/json", + } + + def _body(self, kwargs: Any) -> Dict: + return kwargs + + +def _parse_stream(rbody: Iterator[bytes]) -> Iterator[str]: + for line in rbody: + _line = _parse_stream_helper(line) + if _line is not None: + yield _line + + +async def _parse_stream_async(rbody: aiohttp.StreamReader) -> AsyncIterator[str]: + async for line in rbody: + _line = _parse_stream_helper(line) + if _line is not None: + yield _line + + +def _parse_stream_helper(line: bytes) -> Optional[str]: + if line and line.startswith(b"data:"): + if line.startswith(b"data: "): + # SSE event may be valid when it contain whitespace + line = line[len(b"data: ") :] + else: + line = line[len(b"data:") :] + if line.strip() == b"[DONE]": + # return here will cause GeneratorExit exception in urllib3 + # and it will close http connection with TCP Reset + return None + else: + return line.decode("utf-8") + return None + + +def _handle_sse_line(line: str) -> Optional[BaseMessageChunk]: + try: + obj = json.loads(line) + default_chunk_class = AIMessageChunk + delta = obj.get("choices", [{}])[0].get("delta", {}) + return _convert_delta_to_message_chunk(delta, default_chunk_class) + except Exception: + return None diff --git a/libs/community/tests/integration_tests/chat_models/test_deepinfra.py b/libs/community/tests/integration_tests/chat_models/test_deepinfra.py new file mode 100644 index 0000000000000..0fa4593ace8ab --- /dev/null +++ b/libs/community/tests/integration_tests/chat_models/test_deepinfra.py @@ -0,0 +1,65 @@ +"""Test ChatDeepInfra wrapper.""" +from langchain_core.messages import BaseMessage, HumanMessage +from langchain_core.outputs import ChatGeneration, LLMResult + +from langchain_community.chat_models.deepinfra import ChatDeepInfra +from tests.unit_tests.callbacks.fake_callback_handler import FakeCallbackHandler + + +def test_chat_deepinfra() -> None: + """Test valid call to DeepInfra.""" + chat = ChatDeepInfra( + max_tokens=10, + ) + response = chat.invoke([HumanMessage(content="Hello")]) + assert isinstance(response, BaseMessage) + assert isinstance(response.content, str) + + +def test_chat_deepinfra_streaming() -> None: + callback_handler = FakeCallbackHandler() + chat = ChatDeepInfra( + callbacks=[callback_handler], + streaming=True, + max_tokens=10, + ) + response = chat.invoke([HumanMessage(content="Hello")]) + assert callback_handler.llm_streams > 0 + assert isinstance(response, BaseMessage) + + +async def test_async_chat_deepinfra() -> None: + """Test async generation.""" + chat = ChatDeepInfra( + max_tokens=10, + ) + message = HumanMessage(content="Hello") + response = await chat.agenerate([[message]]) + assert isinstance(response, LLMResult) + assert len(response.generations) == 1 + assert len(response.generations[0]) == 1 + generation = response.generations[0][0] + assert isinstance(generation, ChatGeneration) + assert isinstance(generation.text, str) + assert generation.text == generation.message.content + + +async def test_async_chat_deepinfra_streaming() -> None: + callback_handler = FakeCallbackHandler() + chat = ChatDeepInfra( + # model="meta-llama/Llama-2-7b-chat-hf", + callbacks=[callback_handler], + max_tokens=10, + streaming=True, + timeout=5, + ) + message = HumanMessage(content="Hello") + response = await chat.agenerate([[message]]) + assert callback_handler.llm_streams > 0 + assert isinstance(response, LLMResult) + assert len(response.generations) == 1 + assert len(response.generations[0]) == 1 + generation = response.generations[0][0] + assert isinstance(generation, ChatGeneration) + assert isinstance(generation.text, str) + assert generation.text == generation.message.content diff --git a/libs/community/tests/integration_tests/embeddings/test_deepinfra.py b/libs/community/tests/integration_tests/embeddings/test_deepinfra.py index 8b3fe25e6675e..f3a418ed2391c 100644 --- a/libs/community/tests/integration_tests/embeddings/test_deepinfra.py +++ b/libs/community/tests/integration_tests/embeddings/test_deepinfra.py @@ -5,7 +5,7 @@ def test_deepinfra_call() -> None: """Test valid call to DeepInfra.""" - deepinfra_emb = DeepInfraEmbeddings(model_id="sentence-transformers/clip-ViT-B-32") + deepinfra_emb = DeepInfraEmbeddings(model_id="BAAI/bge-base-en-v1.5") r1 = deepinfra_emb.embed_documents( [ "Alpha is the first letter of Greek alphabet", @@ -13,7 +13,7 @@ def test_deepinfra_call() -> None: ] ) assert len(r1) == 2 - assert len(r1[0]) == 512 - assert len(r1[1]) == 512 + assert len(r1[0]) == 768 + assert len(r1[1]) == 768 r2 = deepinfra_emb.embed_query("What is the third letter of Greek alphabet") - assert len(r2) == 512 + assert len(r2) == 768 diff --git a/libs/community/tests/integration_tests/llms/test_deepinfra.py b/libs/community/tests/integration_tests/llms/test_deepinfra.py index 08b5e566e8006..54057e657e6ea 100644 --- a/libs/community/tests/integration_tests/llms/test_deepinfra.py +++ b/libs/community/tests/integration_tests/llms/test_deepinfra.py @@ -5,13 +5,13 @@ def test_deepinfra_call() -> None: """Test valid call to DeepInfra.""" llm = DeepInfra(model_id="meta-llama/Llama-2-7b-chat-hf") - output = llm("What is 2 + 2?") + output = llm.invoke("What is 2 + 2?") assert isinstance(output, str) async def test_deepinfra_acall() -> None: llm = DeepInfra(model_id="meta-llama/Llama-2-7b-chat-hf") - output = await llm.apredict("What is 2 + 2?") + output = await llm.ainvoke("What is 2 + 2?") assert llm._llm_type == "deepinfra" assert isinstance(output, str) diff --git a/libs/community/tests/unit_tests/chat_models/test_imports.py b/libs/community/tests/unit_tests/chat_models/test_imports.py index 031fb96e8937f..187459afd5019 100644 --- a/libs/community/tests/unit_tests/chat_models/test_imports.py +++ b/libs/community/tests/unit_tests/chat_models/test_imports.py @@ -10,6 +10,7 @@ "ChatAnthropic", "ChatCohere", "ChatDatabricks", + "ChatDeepInfra", "ChatGooglePalm", "ChatHuggingFace", "ChatMlflow", From 8569b8f680447ba8eefb279d0ea09601166a9f08 Mon Sep 17 00:00:00 2001 From: Max Jakob <max.jakob@elastic.co> Date: Mon, 22 Jan 2024 20:26:18 +0100 Subject: [PATCH 146/215] community[patch]: ElasticsearchStore enable max inner product (#16393) Enable max inner product for approximate retrieval strategy. For exact strategy we lack the necessary `maxInnerProduct` function in the Painless scripting language, this is why we do not add it there. Similarity docs: https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params --------- Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com> Co-authored-by: Joe McElroy <joseph.mcelroy@elastic.co> --- .../langchain_community/vectorstores/elasticsearch.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/elasticsearch.py b/libs/community/langchain_community/vectorstores/elasticsearch.py index 7664ae24febf6..2f8a9e2469bbb 100644 --- a/libs/community/langchain_community/vectorstores/elasticsearch.py +++ b/libs/community/langchain_community/vectorstores/elasticsearch.py @@ -214,6 +214,8 @@ def index( similarityAlgo = "l2_norm" elif similarity is DistanceStrategy.DOT_PRODUCT: similarityAlgo = "dot_product" + elif similarity is DistanceStrategy.MAX_INNER_PRODUCT: + similarityAlgo = "max_inner_product" else: raise ValueError(f"Similarity {similarity} not supported.") @@ -412,7 +414,7 @@ class ElasticsearchStore(VectorStore): distance_strategy: Optional. Distance strategy to use when searching the index. Defaults to COSINE. Can be one of COSINE, - EUCLIDEAN_DISTANCE, or DOT_PRODUCT. + EUCLIDEAN_DISTANCE, MAX_INNER_PRODUCT or DOT_PRODUCT. If you want to use a cloud hosted Elasticsearch instance, you can pass in the cloud_id argument instead of the es_url argument. @@ -508,6 +510,7 @@ def __init__( DistanceStrategy.COSINE, DistanceStrategy.DOT_PRODUCT, DistanceStrategy.EUCLIDEAN_DISTANCE, + DistanceStrategy.MAX_INNER_PRODUCT, ] ] = None, strategy: BaseRetrievalStrategy = ApproxRetrievalStrategy(), @@ -1128,7 +1131,8 @@ def from_texts( distance_strategy: Optional. Name of the distance strategy to use. Defaults to "COSINE". can be one of "COSINE", - "EUCLIDEAN_DISTANCE", "DOT_PRODUCT". + "EUCLIDEAN_DISTANCE", "DOT_PRODUCT", + "MAX_INNER_PRODUCT". bulk_kwargs: Optional. Additional arguments to pass to Elasticsearch bulk. """ From 7ecd2f22acc93aa47894ff9c7d83ce5ebce6b2e1 Mon Sep 17 00:00:00 2001 From: JaguarDB <115371133+fserv@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:28:38 -0800 Subject: [PATCH 147/215] community[patch]: update documentation on jaguar vector store (#16346) - **Description:** update documentation on jaguar vector store: Instruction for setting up jaguar server and usage of text_tag. - **Issue:** - **Dependencies:** - **Twitter handle:** --------- Co-authored-by: JY <jyjy@jaguardb> --- docs/docs/integrations/vectorstores/jaguar.ipynb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/integrations/vectorstores/jaguar.ipynb b/docs/docs/integrations/vectorstores/jaguar.ipynb index 3cf75bd9e0347..0b31894879586 100644 --- a/docs/docs/integrations/vectorstores/jaguar.ipynb +++ b/docs/docs/integrations/vectorstores/jaguar.ipynb @@ -28,6 +28,9 @@ "1. You must install and set up the JaguarDB server and its HTTP gateway server.\n", " Please refer to the instructions in:\n", " [www.jaguardb.com](http://www.jaguardb.com)\n", + " For quick setup in docker environment:\n", + " docker pull jaguardb/jaguardb_with_http\n", + " docker run -d -p 8888:8888 -p 8080:8080 --name jaguardb_with_http jaguardb/jaguardb_with_http\n", "\n", "2. You must install the http client package for JaguarDB:\n", " ```\n", @@ -126,6 +129,8 @@ "Add the texts from the text splitter to our vectorstore\n", "\"\"\"\n", "vectorstore.add_documents(docs)\n", + "# or tag the documents:\n", + "# vectorstore.add_documents(more_docs, text_tag=\"tags to these documents\")\n", "\n", "\"\"\" Get the retriever object \"\"\"\n", "retriever = vectorstore.as_retriever()\n", From a1c0cf21c9c621fae640959aa4210cb22a1fc42c Mon Sep 17 00:00:00 2001 From: Hadi <h1rouhani@gmail.com> Date: Mon, 22 Jan 2024 12:33:00 -0700 Subject: [PATCH 148/215] docs: Update import library for StreamlitCallbackHandler (#16401) - **Description:** Some code sources have been moved from `langchain` to `langchain_community` and so the documentation is not yet up-to-date. This is specifically true for `StreamlitCallbackHandler` which returns a `warning` message if not loaded from `langchain_community`., - **Issue:** I don't see a # issue that could address this problem but perhaps #10744, - **Dependencies:** Since it's a documentation change no dependencies are required --- docs/docs/integrations/callbacks/streamlit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/callbacks/streamlit.md b/docs/docs/integrations/callbacks/streamlit.md index 776f0f6d9c26a..47b39cfd0b6a6 100644 --- a/docs/docs/integrations/callbacks/streamlit.md +++ b/docs/docs/integrations/callbacks/streamlit.md @@ -46,7 +46,7 @@ thoughts and actions live in your app. ```python from langchain_openai import OpenAI from langchain.agents import AgentType, initialize_agent, load_tools -from langchain.callbacks import StreamlitCallbackHandler +from langchain_community.callbacks import StreamlitCallbackHandler import streamlit as st llm = OpenAI(temperature=0, streaming=True) From 369e90d42744a8a8c4877de3d02c83b0927f74c9 Mon Sep 17 00:00:00 2001 From: Lance Martin <122662504+rlancemartin@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:33:13 -0800 Subject: [PATCH 149/215] docs: Minor update to Robocorp toolkit docs (#16399) --- .../docs/integrations/toolkits/robocorp.ipynb | 101 ++++++++++++++++-- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/docs/docs/integrations/toolkits/robocorp.ipynb b/docs/docs/integrations/toolkits/robocorp.ipynb index 56db35cc5ca54..2dc39ea75e62c 100644 --- a/docs/docs/integrations/toolkits/robocorp.ipynb +++ b/docs/docs/integrations/toolkits/robocorp.ipynb @@ -9,9 +9,11 @@ "\n", "This notebook covers how to get started with [Robocorp Action Server](https://github.com/robocorp/robocorp) action toolkit and LangChain.\n", "\n", + "Robocorp is the easiest way to extend the capabilities of AI agents, assistants and copilots with custom actions.\n", + "\n", "## Installation\n", "\n", - "First, see the [Robocorp Quickstart](https://github.com/robocorp/robocorp#quickstart) on how to setup Action Server and create your Actions.\n", + "First, see the [Robocorp Quickstart](https://github.com/robocorp/robocorp#quickstart) on how to setup `Action Server` and create your Actions.\n", "\n", "In your LangChain application, install the `langchain-robocorp` package: " ] @@ -20,13 +22,61 @@ "cell_type": "code", "execution_count": null, "id": "4c3bef91", - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ "# Install package\n", "%pip install --upgrade --quiet langchain-robocorp" ] }, + { + "cell_type": "markdown", + "id": "dd53ad19-4a62-46d1-a2f7-151cfd282590", + "metadata": {}, + "source": [ + "When you create the new `Action Server` following the above quickstart.\n", + "\n", + "It will create a directory with files, including `action.py`.\n", + "\n", + "We can add python function as actions as shown [here](https://github.com/robocorp/robocorp/tree/master/actions#describe-your-action).\n", + "\n", + "Let's add a dummy function to `action.py`.\n", + "\n", + "```\n", + "@action\n", + "def get_weather_forecast(city: str, days: int, scale: str = \"celsius\") -> str:\n", + " \"\"\"\n", + " Returns weather conditions forecast for a given city.\n", + "\n", + " Args:\n", + " city (str): Target city to get the weather conditions for\n", + " days: How many day forecast to return\n", + " scale (str): Temperature scale to use, should be one of \"celsius\" or \"fahrenheit\"\n", + "\n", + " Returns:\n", + " str: The requested weather conditions forecast\n", + " \"\"\"\n", + " return \"75F and sunny :)\"\n", + "```\n", + "\n", + "We then start the server:\n", + "\n", + "```\n", + "action-server start\n", + "```\n", + "\n", + "And we can see: \n", + "\n", + "```\n", + "Found new action: get_weather_forecast\n", + "\n", + "```\n", + "\n", + "Test locally by going to the server running at `http://localhost:8080` and use the UI to run the function." + ] + }, { "cell_type": "markdown", "id": "2b4f3e15", @@ -38,17 +88,47 @@ "\n", "- `LANGCHAIN_TRACING_V2=true`: To enable LangSmith log run tracing that can also be bind to respective Action Server action run logs. See [LangSmith documentation](https://docs.smith.langchain.com/tracing#log-runs) for more.\n", "\n", - "## Usage" + "## Usage\n", + "\n", + "We started the local action server, above, running on `http://localhost:8080`." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "62e0dbc3", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `robocorp_action_server_get_weather_forecast` with `{'city': 'San Francisco', 'days': 1, 'scale': 'fahrenheit'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m\"75F and sunny :)\"\u001b[0m\u001b[32;1m\u001b[1;3mThe current weather today in San Francisco is 75F and sunny.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'What is the current weather today in San Francisco in fahrenheit?',\n", + " 'output': 'The current weather today in San Francisco is 75F and sunny.'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from langchain.agents import AgentExecutor, OpenAIFunctionsAgent\n", "from langchain.chat_models import ChatOpenAI\n", @@ -69,8 +149,7 @@ "\n", "executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n", "\n", - "\n", - "executor.invoke(\"What is the current date?\")" + "executor.invoke(\"What is the current weather today in San Francisco in fahrenheit?\")" ] }, { @@ -80,12 +159,14 @@ "source": [ "### Single input tools\n", "\n", - "By default `toolkit.get_tools()` will return the actions as Structured Tools. To return single input tools, pass a Chat model to be used for processing the inputs." + "By default `toolkit.get_tools()` will return the actions as Structured Tools. \n", + "\n", + "To return single input tools, pass a Chat model to be used for processing the inputs." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "1dc7db86", "metadata": {}, "outputs": [], @@ -112,7 +193,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.9.16" } }, "nbformat": 4, From 01c2f27ffa87c4225d4f6ae2e89887e1424990dc Mon Sep 17 00:00:00 2001 From: Katarina Supe <61758502+katarinasupe@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:33:28 +0100 Subject: [PATCH 150/215] community[patch]: Update Memgraph support (#16360) - **Description:** I removed two queries to the database and left just one whose results were formatted afterward into other type of schema (avoided two calls to DB) - **Issue:** / - **Dependencies:** / - **Twitter handle:** @supe_katarina --- .../graphs/memgraph_graph.py | 45 ++++++++++---- .../integration_tests/graphs/test_memgraph.py | 62 +++++++++++++++++++ 2 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 libs/community/tests/integration_tests/graphs/test_memgraph.py diff --git a/libs/community/langchain_community/graphs/memgraph_graph.py b/libs/community/langchain_community/graphs/memgraph_graph.py index 2df4612a2c09b..34e9f7145bb22 100644 --- a/libs/community/langchain_community/graphs/memgraph_graph.py +++ b/libs/community/langchain_community/graphs/memgraph_graph.py @@ -1,12 +1,6 @@ from langchain_community.graphs.neo4j_graph import Neo4jGraph SCHEMA_QUERY = """ -CALL llm_util.schema("prompt_ready") -YIELD * -RETURN * -""" - -RAW_SCHEMA_QUERY = """ CALL llm_util.schema("raw") YIELD * RETURN * @@ -39,10 +33,39 @@ def refresh_schema(self) -> None: Refreshes the Memgraph graph schema information. """ - db_schema = self.query(SCHEMA_QUERY)[0].get("schema") - assert db_schema is not None - self.schema = db_schema - - db_structured_schema = self.query(RAW_SCHEMA_QUERY)[0].get("schema") + db_structured_schema = self.query(SCHEMA_QUERY)[0].get("schema") assert db_structured_schema is not None self.structured_schema = db_structured_schema + + # Format node properties + formatted_node_props = [] + + for node_name, properties in db_structured_schema["node_props"].items(): + formatted_node_props.append( + f"Node name: '{node_name}', Node properties: {properties}" + ) + + # Format relationship properties + formatted_rel_props = [] + for rel_name, properties in db_structured_schema["rel_props"].items(): + formatted_rel_props.append( + f"Relationship name: '{rel_name}', " + f"Relationship properties: {properties}" + ) + + # Format relationships + formatted_rels = [ + f"(:{rel['start']})-[:{rel['type']}]->(:{rel['end']})" + for rel in db_structured_schema["relationships"] + ] + + self.schema = "\n".join( + [ + "Node properties are the following:", + *formatted_node_props, + "Relationship properties are the following:", + *formatted_rel_props, + "The relationships are the following:", + *formatted_rels, + ] + ) diff --git a/libs/community/tests/integration_tests/graphs/test_memgraph.py b/libs/community/tests/integration_tests/graphs/test_memgraph.py new file mode 100644 index 0000000000000..663f974d3f106 --- /dev/null +++ b/libs/community/tests/integration_tests/graphs/test_memgraph.py @@ -0,0 +1,62 @@ +import os + +from langchain_community.graphs import MemgraphGraph + + +def test_cypher_return_correct_schema() -> None: + """Test that chain returns direct results.""" + url = os.environ.get("MEMGRAPH_URI", "bolt://localhost:7687") + username = os.environ.get("MEMGRAPH_USERNAME", "") + password = os.environ.get("MEMGRAPH_PASSWORD", "") + assert url is not None + assert username is not None + assert password is not None + + graph = MemgraphGraph( + url=url, + username=username, + password=password, + ) + # Delete all nodes in the graph + graph.query("MATCH (n) DETACH DELETE n") + # Create two nodes and a relationship + graph.query( + """ + CREATE (la:LabelA {property_a: 'a'}) + CREATE (lb:LabelB) + CREATE (lc:LabelC) + MERGE (la)-[:REL_TYPE]-> (lb) + MERGE (la)-[:REL_TYPE {rel_prop: 'abc'}]-> (lc) + """ + ) + # Refresh schema information + graph.refresh_schema() + relationships = graph.query( + "CALL llm_util.schema('raw') YIELD schema " + "WITH schema.relationships AS relationships " + "UNWIND relationships AS relationship " + "RETURN relationship['start'] AS start, " + "relationship['type'] AS type, " + "relationship['end'] AS end " + "ORDER BY start, type, end;" + ) + + node_props = graph.query( + "CALL llm_util.schema('raw') YIELD schema " + "WITH schema.node_props AS nodes " + "WITH nodes['LabelA'] AS properties " + "UNWIND properties AS property " + "RETURN property['property'] AS prop, " + "property['type'] AS type " + "ORDER BY prop ASC;" + ) + + expected_relationships = [ + {"start": "LabelA", "type": "REL_TYPE", "end": "LabelB"}, + {"start": "LabelA", "type": "REL_TYPE", "end": "LabelC"}, + ] + + expected_node_props = [{"prop": "property_a", "type": "str"}] + + assert relationships == expected_relationships + assert node_props == expected_node_props From 1b9001db47241d0bd602e92a594cc3e5e3dc51ba Mon Sep 17 00:00:00 2001 From: Piotr Mardziel <piotrm@gmail.com> Date: Mon, 22 Jan 2024 11:34:13 -0800 Subject: [PATCH 151/215] core[patch]: preserve inspect.iscoroutinefunction with @deprecated decorator (#16295) Adjusted `deprecate` decorator to make sure decorated async functions are still recognized as "coroutinefunction" by `inspect`. Before change, functions such as `LLMChain.acall` which are decorated as deprecated are not recognized as coroutine functions. After the change, they are recognized: ```python import inspect from langchain import LLMChain # Is false before change but true after. inspect.iscoroutinefunction(LLMChain.acall) ``` --- libs/core/langchain_core/_api/deprecation.py | 14 ++++- .../tests/unit_tests/_api/test_deprecation.py | 61 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/libs/core/langchain_core/_api/deprecation.py b/libs/core/langchain_core/_api/deprecation.py index b9a0c39e485fe..b4d56b70cb494 100644 --- a/libs/core/langchain_core/_api/deprecation.py +++ b/libs/core/langchain_core/_api/deprecation.py @@ -144,6 +144,15 @@ def warning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any: emit_warning() return wrapped(*args, **kwargs) + async def awarning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any: + """Same as warning_emitting_wrapper, but for async functions.""" + + nonlocal warned + if not warned and not is_caller_internal(): + warned = True + emit_warning() + return await wrapped(*args, **kwargs) + if isinstance(obj, type): if not _obj_type: _obj_type = "class" @@ -256,7 +265,10 @@ def finalize( # type: ignore f" {details}" ) - return finalize(warning_emitting_wrapper, new_doc) + if inspect.iscoroutinefunction(obj): + return finalize(awarning_emitting_wrapper, new_doc) + else: + return finalize(warning_emitting_wrapper, new_doc) return deprecate diff --git a/libs/core/tests/unit_tests/_api/test_deprecation.py b/libs/core/tests/unit_tests/_api/test_deprecation.py index dc7f40de133d8..d26b18c3ad448 100644 --- a/libs/core/tests/unit_tests/_api/test_deprecation.py +++ b/libs/core/tests/unit_tests/_api/test_deprecation.py @@ -1,3 +1,4 @@ +import inspect import warnings from typing import Any, Dict @@ -74,6 +75,12 @@ def deprecated_function() -> str: return "This is a deprecated function." +@deprecated(since="2.0.0", removal="3.0.0", pending=False) +async def deprecated_async_function() -> str: + """original doc""" + return "This is a deprecated async function." + + class ClassWithDeprecatedMethods: def __init__(self) -> None: """original doc""" @@ -84,6 +91,11 @@ def deprecated_method(self) -> str: """original doc""" return "This is a deprecated method." + @deprecated(since="2.0.0", removal="3.0.0") + async def deprecated_async_method(self) -> str: + """original doc""" + return "This is a deprecated async method." + @classmethod @deprecated(since="2.0.0", removal="3.0.0") def deprecated_classmethod(cls) -> str: @@ -119,6 +131,30 @@ def test_deprecated_function() -> None: assert isinstance(doc, str) assert doc.startswith("[*Deprecated*] original doc") + assert not inspect.iscoroutinefunction(deprecated_function) + + +@pytest.mark.asyncio +async def test_deprecated_async_function() -> None: + """Test deprecated async function.""" + with warnings.catch_warnings(record=True) as warning_list: + warnings.simplefilter("always") + assert ( + await deprecated_async_function() == "This is a deprecated async function." + ) + assert len(warning_list) == 1 + warning = warning_list[0].message + assert str(warning) == ( + "The function `deprecated_async_function` was deprecated " + "in LangChain 2.0.0 and will be removed in 3.0.0" + ) + + doc = deprecated_function.__doc__ + assert isinstance(doc, str) + assert doc.startswith("[*Deprecated*] original doc") + + assert inspect.iscoroutinefunction(deprecated_async_function) + def test_deprecated_method() -> None: """Test deprecated method.""" @@ -137,6 +173,31 @@ def test_deprecated_method() -> None: assert isinstance(doc, str) assert doc.startswith("[*Deprecated*] original doc") + assert not inspect.iscoroutinefunction(obj.deprecated_method) + + +@pytest.mark.asyncio +async def test_deprecated_async_method() -> None: + """Test deprecated async method.""" + with warnings.catch_warnings(record=True) as warning_list: + warnings.simplefilter("always") + obj = ClassWithDeprecatedMethods() + assert ( + await obj.deprecated_async_method() == "This is a deprecated async method." + ) + assert len(warning_list) == 1 + warning = warning_list[0].message + assert str(warning) == ( + "The function `deprecated_async_method` was deprecated in " + "LangChain 2.0.0 and will be removed in 3.0.0" + ) + + doc = obj.deprecated_method.__doc__ + assert isinstance(doc, str) + assert doc.startswith("[*Deprecated*] original doc") + + assert inspect.iscoroutinefunction(obj.deprecated_async_method) + def test_deprecated_classmethod() -> None: """Test deprecated classmethod.""" From aad2aa718818f7cfe8cb307346f51e0258bbb9f4 Mon Sep 17 00:00:00 2001 From: Guillem Orellana Trullols <guillem.orellana@gmail.com> Date: Mon, 22 Jan 2024 20:37:23 +0100 Subject: [PATCH 152/215] community[patch]: BedrockChat -> Support Titan express as chat model (#15408) Titan Express model was not supported as a chat model because LangChain messages were not "translated" to a text prompt. Co-authored-by: Guillem Orellana Trullols <guillem.orellana_trullols@siemens.com> --- .../chat_models/bedrock.py | 6 +++++ .../langchain_community/llms/bedrock.py | 6 +++-- .../unit_tests/chat_models/test_bedrock.py | 25 ++++++++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/libs/community/langchain_community/chat_models/bedrock.py b/libs/community/langchain_community/chat_models/bedrock.py index 49b7acad19a02..5538372272b7b 100644 --- a/libs/community/langchain_community/chat_models/bedrock.py +++ b/libs/community/langchain_community/chat_models/bedrock.py @@ -32,6 +32,12 @@ def convert_messages_to_prompt( prompt = convert_messages_to_prompt_anthropic(messages=messages) elif provider == "meta": prompt = convert_messages_to_prompt_llama(messages=messages) + elif provider == "amazon": + prompt = convert_messages_to_prompt_anthropic( + messages=messages, + human_prompt="\n\nUser:", + ai_prompt="\n\nBot:", + ) else: raise NotImplementedError( f"Provider {provider} model does not support chat." diff --git a/libs/community/langchain_community/llms/bedrock.py b/libs/community/langchain_community/llms/bedrock.py index ddc15d5ef1cbc..3f10b09b63e37 100644 --- a/libs/community/langchain_community/llms/bedrock.py +++ b/libs/community/langchain_community/llms/bedrock.py @@ -272,10 +272,12 @@ def _prepare_input_and_invoke( try: response = self.client.invoke_model( - body=body, modelId=self.model_id, accept=accept, contentType=contentType + body=body, + modelId=self.model_id, + accept=accept, + contentType=contentType, ) text = LLMInputOutputAdapter.prepare_output(provider, response) - except Exception as e: raise ValueError(f"Error raised by bedrock service: {e}").with_traceback( e.__traceback__ diff --git a/libs/community/tests/unit_tests/chat_models/test_bedrock.py b/libs/community/tests/unit_tests/chat_models/test_bedrock.py index 93f03b8ecedc1..b515c99e5ad80 100644 --- a/libs/community/tests/unit_tests/chat_models/test_bedrock.py +++ b/libs/community/tests/unit_tests/chat_models/test_bedrock.py @@ -37,17 +37,24 @@ def test_formatting(messages: List[BaseMessage], expected: str) -> None: assert result == expected -def test_anthropic_bedrock() -> None: +@pytest.mark.parametrize( + "model_id", + ["anthropic.claude-v2", "amazon.titan-text-express-v1"], +) +def test_different_models_bedrock(model_id: str) -> None: + provider = model_id.split(".")[0] client = MagicMock() - respbody = MagicMock( - read=MagicMock( - return_value=MagicMock( - decode=MagicMock(return_value=b'{"completion":"Hi back"}') - ) + respbody = MagicMock() + if provider == "anthropic": + respbody.read.return_value = MagicMock( + decode=MagicMock(return_value=b'{"completion":"Hi back"}'), ) - ) - client.invoke_model.return_value = {"body": respbody} - model = BedrockChat(model_id="anthropic.claude-v2", client=client) + client.invoke_model.return_value = {"body": respbody} + elif provider == "amazon": + respbody.read.return_value = '{"results": [{"outputText": "Hi back"}]}' + client.invoke_model.return_value = {"body": respbody} + + model = BedrockChat(model_id=model_id, client=client) # should not throw an error model.invoke("hello there") From 6b2a57161af92965ca4c00c06508a88372967cf0 Mon Sep 17 00:00:00 2001 From: Eli Lucherini <elucherini@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:38:11 -0800 Subject: [PATCH 153/215] community[patch]: allow additional kwargs in MlflowEmbeddings for compatibility with Cohere API (#15242) - **Description:** add support for kwargs in`MlflowEmbeddings` `embed_document()` and `embed_query()` so that all the arguments required by Cohere API (and others?) can be passed down to the server. - **Issue:** #15234 - **Dependencies:** MLflow with MLflow Deployments (`pip install mlflow[genai]`) **Tests** Now this code [adapted from the docs](https://python.langchain.com/docs/integrations/providers/mlflow#embeddings-example) for the Cohere API works locally. ```python """ Setup ----- export COHERE_API_KEY=... mlflow deployments start-server --config-path examples/deployments/cohere/config.yaml Run --- python /path/to/this/file.py """ embeddings = MlflowCohereEmbeddings(target_uri="http://127.0.0.1:5000", endpoint="embeddings") print(embeddings.embed_query("hello")[:3]) print(embeddings.embed_documents(["hello", "world"])[0][:3]) ``` Output ``` [0.060455322, 0.028793335, -0.025848389] [0.031707764, 0.021057129, -0.009361267] ``` --- .../embeddings/__init__.py | 6 ++++- .../langchain_community/embeddings/mlflow.py | 22 +++++++++++++++---- .../unit_tests/embeddings/test_imports.py | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/libs/community/langchain_community/embeddings/__init__.py b/libs/community/langchain_community/embeddings/__init__.py index 9b9deba027c19..f39787b3556d1 100644 --- a/libs/community/langchain_community/embeddings/__init__.py +++ b/libs/community/langchain_community/embeddings/__init__.py @@ -57,7 +57,10 @@ from langchain_community.embeddings.llm_rails import LLMRailsEmbeddings from langchain_community.embeddings.localai import LocalAIEmbeddings from langchain_community.embeddings.minimax import MiniMaxEmbeddings -from langchain_community.embeddings.mlflow import MlflowEmbeddings +from langchain_community.embeddings.mlflow import ( + MlflowCohereEmbeddings, + MlflowEmbeddings, +) from langchain_community.embeddings.mlflow_gateway import MlflowAIGatewayEmbeddings from langchain_community.embeddings.modelscope_hub import ModelScopeEmbeddings from langchain_community.embeddings.mosaicml import MosaicMLInstructorEmbeddings @@ -102,6 +105,7 @@ "LLMRailsEmbeddings", "HuggingFaceHubEmbeddings", "MlflowEmbeddings", + "MlflowCohereEmbeddings", "MlflowAIGatewayEmbeddings", "ModelScopeEmbeddings", "TensorflowHubEmbeddings", diff --git a/libs/community/langchain_community/embeddings/mlflow.py b/libs/community/langchain_community/embeddings/mlflow.py index 0ae46bcffdb79..6b24dacb02575 100644 --- a/libs/community/langchain_community/embeddings/mlflow.py +++ b/libs/community/langchain_community/embeddings/mlflow.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Iterator, List +from typing import Any, Dict, Iterator, List from urllib.parse import urlparse from langchain_core.embeddings import Embeddings @@ -34,6 +34,10 @@ class MlflowEmbeddings(Embeddings, BaseModel): target_uri: str """The target URI to use.""" _client: Any = PrivateAttr() + """The parameters to use for queries.""" + query_params: Dict[str, str] = {} + """The parameters to use for documents.""" + documents_params: Dict[str, str] = {} def __init__(self, **kwargs: Any): super().__init__(**kwargs) @@ -63,12 +67,22 @@ def _validate_uri(self) -> None: f"The scheme must be one of {allowed}." ) - def embed_documents(self, texts: List[str]) -> List[List[float]]: + def embed(self, texts: List[str], params: Dict[str, str]) -> List[List[float]]: embeddings: List[List[float]] = [] for txt in _chunk(texts, 20): - resp = self._client.predict(endpoint=self.endpoint, inputs={"input": txt}) + resp = self._client.predict( + endpoint=self.endpoint, inputs={"input": txt, **params} + ) embeddings.extend(r["embedding"] for r in resp["data"]) return embeddings + def embed_documents(self, texts: List[str]) -> List[List[float]]: + return self.embed(texts, params=self.documents_params) + def embed_query(self, text: str) -> List[float]: - return self.embed_documents([text])[0] + return self.embed([text], params=self.query_params)[0] + + +class MlflowCohereEmbeddings(MlflowEmbeddings): + query_params: Dict[str, str] = {"input_type": "search_query"} + documents_params: Dict[str, str] = {"input_type": "search_document"} diff --git a/libs/community/tests/unit_tests/embeddings/test_imports.py b/libs/community/tests/unit_tests/embeddings/test_imports.py index 6aac6609a9968..dee9b1ba836fd 100644 --- a/libs/community/tests/unit_tests/embeddings/test_imports.py +++ b/libs/community/tests/unit_tests/embeddings/test_imports.py @@ -18,6 +18,7 @@ "HuggingFaceHubEmbeddings", "MlflowAIGatewayEmbeddings", "MlflowEmbeddings", + "MlflowCohereEmbeddings", "ModelScopeEmbeddings", "TensorflowHubEmbeddings", "SagemakerEndpointEmbeddings", From 873de14cd8dc239682614453eb60a8bd01136d56 Mon Sep 17 00:00:00 2001 From: Omar-aly <75560551+OmarAly23@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:40:08 +0300 Subject: [PATCH 154/215] docs: update vectorstores/llm_rails integration doc (#16199) Description: - Updated the docs for the vectorstores integration module llm_rails.ipynb Issue: - [Connected to Issue #15664](https://github.com/langchain-ai/langchain/issues/15664) Dependencies: - N/A Co-authored-by: omaraly23 <112936089+omaraly22@users.noreply.github.com> --- .../integrations/vectorstores/llm_rails.ipynb | 99 ++++++++++++++----- 1 file changed, 74 insertions(+), 25 deletions(-) diff --git a/docs/docs/integrations/vectorstores/llm_rails.ipynb b/docs/docs/integrations/vectorstores/llm_rails.ipynb index 8e22959353b66..1ac0a57b6f4e8 100644 --- a/docs/docs/integrations/vectorstores/llm_rails.ipynb +++ b/docs/docs/integrations/vectorstores/llm_rails.ipynb @@ -68,7 +68,44 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 19, + "id": "0fda552b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting tika\n", + " Downloading tika-2.6.0.tar.gz (27 kB)\n", + " Preparing metadata (setup.py) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: setuptools in /Users/omaraly/anaconda3/lib/python3.11/site-packages (from tika) (68.2.2)\n", + "Requirement already satisfied: requests in /Users/omaraly/anaconda3/lib/python3.11/site-packages (from tika) (2.31.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/omaraly/anaconda3/lib/python3.11/site-packages (from requests->tika) (2.1.1)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/omaraly/anaconda3/lib/python3.11/site-packages (from requests->tika) (3.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/omaraly/anaconda3/lib/python3.11/site-packages (from requests->tika) (1.26.16)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/omaraly/anaconda3/lib/python3.11/site-packages (from requests->tika) (2022.12.7)\n", + "Building wheels for collected packages: tika\n", + " Building wheel for tika (setup.py) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for tika: filename=tika-2.6.0-py3-none-any.whl size=32621 sha256=b3f03c9dbd7f347d712c49027704d48f1a368f31560be9b4ee131f79a52e176f\n", + " Stored in directory: /Users/omaraly/Library/Caches/pip/wheels/27/ba/2f/37420d1191bdae5e855d69b8e913673045bfd395cbd78ad697\n", + "Successfully built tika\n", + "Installing collected packages: tika\n", + "Successfully installed tika-2.6.0\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install tika" + ] + }, + { + "cell_type": "code", + "execution_count": 37, "id": "920f4644", "metadata": {}, "outputs": [], @@ -100,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 39, "id": "a8c513ab", "metadata": { "ExecuteTime": { @@ -117,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 40, "id": "fc516993", "metadata": { "ExecuteTime": { @@ -131,29 +168,37 @@ "name": "stdout", "output_type": "stream", "text": [ - "Others may not be democratic but nevertheless depend upon a rules-based international system.\n", + "6 N A T I O N A L S E C U R I T Y S T R A T E G Y Page 7 \n", + "\n", + "This National Security Strategy lays out our plan to achieve a better future of a free, open, secure, and prosperous world.\n", + "\n", + "Our strategy is rooted in our national interests: to protect the security of the American people; to expand economic prosperity and opportunity; and to realize and defend the democratic values at the heart of the American way of life.\n", + "\n", + "We can do none of this alone and we do not have to.\n", + "\n", + "Most nations around the world define their interests in ways that are compatible with ours.\n", "\n", - "Yet what we share in common, and the prospect of a freer and more open world, makes such a broad coalition necessary and worthwhile.\n", + "We will build the strongest and broadest possible coalition of nations that seek to cooperate with each other, while competing with those powers that offer a darker vision and thwarting their efforts to threaten our interests.\n", "\n", - "We will listen to and consider ideas that our partners suggest about how to do this.\n", + "Our Enduring Role The need for a strong and purposeful American role in the world has never been greater.\n", "\n", - "Building this inclusive coalition requires reinforcing the multilateral system to uphold the founding principles of the United Nations, including respect for international law.\n", + "The world is becoming more divided and unstable.\n", "\n", - "141 countries expressed support at the United Nations General Assembly for a resolution condemning Russia’s unprovoked aggression against Ukraine.\n", + "Global increases in inflation since the COVID-19 pandemic began have made life more difficult for many.\n", "\n", - "We continue to demonstrate this approach by engaging all regions across all issues, not in terms of what we are against but what we are for.\n", + "The basic laws and principles governing relations among nations, including the United Nations Charter and the protection it affords all states from being invaded by their neighbors or having their borders redrawn by force, are under attack.\n", "\n", - "This year, we partnered with ASEAN to advance clean energy infrastructure and maritime security in the region.\n", + "The risk of conflict between major powers is increasing.\n", "\n", - "We kickstarted the Prosper Africa Build Together Campaign to fuel economic growth across the continent and bolster trade and investment in the clean energy, health, and digital technology sectors.\n", + "Democracies and autocracies are engaged in a contest to show which system of governance can best deliver for their people and the world.\n", "\n", - "We are working to develop a partnership with countries on the Atlantic Ocean to establish and carry out a shared approach to advancing our joint development, economic, environmental, scientific, and maritime governance goals.\n", + "Competition to develop and deploy foundational technologies that will transform our security and economy is intensifying.\n", "\n", - "We galvanized regional action to address the core challenges facing the Western Hemisphere by spearheading the Americas Partnership for Economic Prosperity to drive economic recovery and by mobilizing the region behind a bold and unprecedented approach to migration through the Los Angeles Declaration on Migration and Protection.\n", + "Global cooperation on shared interests has frayed, even as the need for that cooperation takes on existential importance.\n", "\n", - "In the Middle East, we have worked to enhance deterrence toward Iran, de-escalate regional conflicts, deepen integration among a diverse set of partners in the region, and bolster energy stability.\n", + "The scale of these changes grows with each passing year, as do the risks of inaction.\n", "\n", - "A prime example of an inclusive coalition is IPEF, which we launched alongside a dozen regional partners that represent 40 percent of the world’s GDP.\n" + "Although the international environment has become more contested, the United States remains the world’s leading power.\n" ] } ], @@ -173,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 41, "id": "8804a21d", "metadata": { "ExecuteTime": { @@ -192,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 42, "id": "756a6887", "metadata": { "ExecuteTime": { @@ -251,7 +296,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 43, "id": "9427195f", "metadata": { "ExecuteTime": { @@ -263,10 +308,10 @@ { "data": { "text/plain": [ - "LLMRailsRetriever(tags=None, metadata=None, vectorstore=<langchain_community.vectorstores.llm_rails.LLMRails object at 0x107b9c040>, search_type='similarity', search_kwargs={'k': 5})" + "LLMRailsRetriever(vectorstore=<langchain_community.vectorstores.llm_rails.LLMRails object at 0x1235b0e50>)" ] }, - "execution_count": 10, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -278,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 44, "id": "f3c70c31", "metadata": { "ExecuteTime": { @@ -290,17 +335,21 @@ { "data": { "text/plain": [ - "Document(page_content='But we will do so as the last resort and only when the objectives and mission are clear and achievable, consistent with our values and laws, alongside non-military tools, and the mission is undertaken with the informed consent of the American people.\\n\\nOur approach to national defense is described in detail in the 2022 National Defense Strategy.\\n\\nOur starting premise is that a powerful U.S. military helps advance and safeguard vital U.S. national interests by backstopping diplomacy, confronting aggression, deterring conflict, projecting strength, and protecting the American people and their economic interests.\\n\\nAmid intensifying competition, the military’s role is to maintain and gain warfighting advantages while limiting those of our competitors.\\n\\nThe military will act urgently to sustain and strengthen deterrence, with the PRC as its pacing challenge.\\n\\nWe will make disciplined choices regarding our national defense and focus our attention on the military’s primary responsibilities: to defend the homeland, and deter attacks and aggression against the United States, our allies and partners, while being prepared to fight and win the Nation’s wars should diplomacy and deterrence fail.\\n\\nTo do so, we will combine our strengths to achieve maximum effect in deterring acts of aggression—an approach we refer to as integrated deterrence (see text box on page 22).\\n\\nWe will operate our military using a campaigning mindset—sequencing logically linked military activities to advance strategy-aligned priorities.\\n\\nAnd, we will build a resilient force and defense ecosystem to ensure we can perform these functions for decades to come.\\n\\nWe ended America’s longest war in Afghanistan, and with it an era of major military operations to remake other societies, even as we have maintained the capacity to address terrorist threats to the American people as they emerge.\\n\\n20 NATIONAL SECURITY STRATEGY Page 21 \\x90\\x90\\x90\\x90\\x90\\x90\\n\\nA combat-credible military is the foundation of deterrence and America’s ability to prevail in conflict.', metadata={'type': 'file', 'url': 'https://cdn.llmrails.com/dst_d94b490c-4638-4247-ad5e-9aa0e7ef53c1/c2d63a2ea3cd406cb522f8312bc1535d', 'name': 'Biden-Harris-Administrations-National-Security-Strategy-10.2022.pdf'})" + "[Document(page_content='But we will do so as the last resort and only when the objectives and mission are clear and achievable, consistent with our values and laws, alongside non-military tools, and the mission is undertaken with the informed consent of the American people.\\n\\nOur approach to national defense is described in detail in the 2022 National Defense Strategy.\\n\\nOur starting premise is that a powerful U.S. military helps advance and safeguard vital U.S. national interests by backstopping diplomacy, confronting aggression, deterring conflict, projecting strength, and protecting the American people and their economic interests.\\n\\nAmid intensifying competition, the military’s role is to maintain and gain warfighting advantages while limiting those of our competitors.\\n\\nThe military will act urgently to sustain and strengthen deterrence, with the PRC as its pacing challenge.\\n\\nWe will make disciplined choices regarding our national defense and focus our attention on the military’s primary responsibilities: to defend the homeland, and deter attacks and aggression against the United States, our allies and partners, while being prepared to fight and win the Nation’s wars should diplomacy and deterrence fail.\\n\\nTo do so, we will combine our strengths to achieve maximum effect in deterring acts of aggression—an approach we refer to as integrated deterrence (see text box on page 22).\\n\\nWe will operate our military using a campaigning mindset—sequencing logically linked military activities to advance strategy-aligned priorities.\\n\\nAnd, we will build a resilient force and defense ecosystem to ensure we can perform these functions for decades to come.\\n\\nWe ended America’s longest war in Afghanistan, and with it an era of major military operations to remake other societies, even as we have maintained the capacity to address terrorist threats to the American people as they emerge.\\n\\n20 NATIONAL SECURITY STRATEGY Page 21 \\x90\\x90\\x90\\x90\\x90\\x90\\n\\nA combat-credible military is the foundation of deterrence and America’s ability to prevail in conflict.', metadata={'type': 'file', 'url': 'https://cdn.llmrails.com/dst_466092be-e79a-49f3-b3e6-50e51ddae186/a63892afdee3469d863520351bd5af9f', 'name': 'Biden-Harris-Administrations-National-Security-Strategy-10.2022.pdf', 'filters': {}}),\n", + " Document(page_content='Your text here', metadata={'type': 'text', 'url': 'https://cdn.llmrails.com/dst_466092be-e79a-49f3-b3e6-50e51ddae186/63c17ac6395e4be1967c63a16356818e', 'name': '71370a91-7f58-4cc7-b2e7-546325960330', 'filters': {}}),\n", + " Document(page_content='Page 1 NATIONAL SECURITY STRATEGY OCTOBER 2022 Page 2 October 12, 2022 From the earliest days of my Presidency, I have argued that our world is at an inflection point.\\n\\nHow we respond to the tremendous challenges and the unprecedented opportunities we face today will determine the direction of our world and impact the security and prosperity of the American people for generations to come.\\n\\nThe 2022 National Security Strategy outlines how my Administration will seize this decisive decade to advance America’s vital interests, position the United States to outmaneuver our geopolitical competitors, tackle shared challenges, and set our world firmly on a path toward a brighter and more hopeful tomorrow.\\n\\nAround the world, the need for American leadership is as great as it has ever been.\\n\\nWe are in the midst of a strategic competition to shape the future of the international order.\\n\\nMeanwhile, shared challenges that impact people everywhere demand increased global cooperation and nations stepping up to their responsibilities at a moment when this has become more difficult.\\n\\nIn response, the United States will lead with our values, and we will work in lockstep with our allies and partners and with all those who share our interests.\\n\\nWe will not leave our future vulnerable to the whims of those who do not share our vision for a world that is free, open, prosperous, and secure.\\n\\nAs the world continues to navigate the lingering impacts of the pandemic and global economic uncertainty, there is no nation better positioned to lead with strength and purpose than the United States of America.\\n\\nFrom the moment I took the oath of office, my Administration has focused on investing in America’s core strategic advantages.\\n\\nOur economy has added 10 million jobs and unemployment rates have reached near record lows.\\n\\nManufacturing jobs have come racing back to the United States.\\n\\nWe’re rebuilding our economy from the bottom up and the middle out.', metadata={'type': 'file', 'url': 'https://cdn.llmrails.com/dst_466092be-e79a-49f3-b3e6-50e51ddae186/a63892afdee3469d863520351bd5af9f', 'name': 'Biden-Harris-Administrations-National-Security-Strategy-10.2022.pdf', 'filters': {}}),\n", + " Document(page_content='Your text here', metadata={'type': 'text', 'url': 'https://cdn.llmrails.com/dst_466092be-e79a-49f3-b3e6-50e51ddae186/8c414a9306e04d47a300f0289ba6e9cf', 'name': 'dacc29f5-8c63-46e0-b5aa-cab2d3c99fb7', 'filters': {}}),\n", + " Document(page_content='To ensure our nuclear deterrent remains responsive to the threats we face, we are modernizing the nuclear Triad, nuclear command, control, and communications, and our nuclear weapons infrastructure, as well as strengthening our extended deterrence commitments to our Allies.\\n\\nWe remain equally committed to reducing the risks of nuclear war.\\n\\nThis includes taking further steps to reduce the role of nuclear weapons in our strategy and pursuing realistic goals for mutual, verifiable arms control, which contribute to our deterrence strategy and strengthen the global non-proliferation regime.\\n\\nThe most important investments are those made in the extraordinary All-Volunteer Force of the Army, Marine Corps, Navy, Air Force, Space Force, Coast Guard—together with our Department of Defense civilian workforce.\\n\\nOur service members are the backbone of America’s national defense and we are committed to their wellbeing and their families while in service and beyond.\\n\\nWe will maintain our foundational principle of civilian control of the military, recognizing that healthy civil-military relations rooted in mutual respect are essential to military effectiveness.\\n\\nWe will strengthen the effectiveness of the force by promoting diversity and inclusion; intensifying our suicide prevention efforts; eliminating the scourges of sexual assault, harassment, and other forms of violence, abuse, and discrimination; and rooting out violent extremism.\\n\\nWe will also uphold our Nation’s sacred obligation to care for veterans and their families when our troops return home.\\n\\nNATIONAL SECURITY STRATEGY 21 Page 22 \\x90\\x90\\x90\\x90\\x90\\x90\\n\\nIntegrated Deterrence The United States has a vital interest in deterring aggression by the PRC, Russia, and other states.\\n\\nMore capable competitors and new strategies of threatening behavior below and above the traditional threshold of conflict mean we cannot afford to rely solely on conventional forces and nuclear deterrence.\\n\\nOur defense strategy must sustain and strengthen deterrence, with the PRC as our pacing challenge.', metadata={'type': 'file', 'url': 'https://cdn.llmrails.com/dst_466092be-e79a-49f3-b3e6-50e51ddae186/a63892afdee3469d863520351bd5af9f', 'name': 'Biden-Harris-Administrations-National-Security-Strategy-10.2022.pdf', 'filters': {}})]" ] }, - "execution_count": 12, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "query = \"What is your approach to national defense\"\n", - "retriever.get_relevant_documents(query)[0]" + "retriever.invoke(query)" ] } ], @@ -320,7 +369,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.11.3" } }, "nbformat": 4, From dd5b8107b1573baac0e6ada2c17256e4e3aa2696 Mon Sep 17 00:00:00 2001 From: Sarthak Chaure <sarchaure911@gmail.com> Date: Tue, 23 Jan 2024 02:40:19 +0530 Subject: [PATCH 155/215] Docs: Updated callbacks/index.mdx (#16404) The callbacks get started demo code was updated , replacing the chain.run() command ( which is now depricated) ,with the updated chain.invoke() command. Solving the following issue : #16379 Twitter/X : @Hazxhx --- docs/docs/modules/callbacks/index.mdx | 34 +++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/docs/modules/callbacks/index.mdx b/docs/docs/modules/callbacks/index.mdx index 9d0a1c78c745f..3a3ba08cfb8b0 100644 --- a/docs/docs/modules/callbacks/index.mdx +++ b/docs/docs/modules/callbacks/index.mdx @@ -95,42 +95,40 @@ prompt = PromptTemplate.from_template("1 + {number} = ") # Constructor callback: First, let's explicitly set the StdOutCallbackHandler when initializing our chain chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler]) -chain.run(number=2) +chain.invoke({"number":2}) # Use verbose flag: Then, let's use the `verbose` flag to achieve the same result chain = LLMChain(llm=llm, prompt=prompt, verbose=True) -chain.run(number=2) +chain.invoke({"number":2}) # Request callbacks: Finally, let's use the request `callbacks` to achieve the same result chain = LLMChain(llm=llm, prompt=prompt) -chain.run(number=2, callbacks=[handler]) +chain.invoke({"number":2}, {"callbacks":[handler]}) + ``` <CodeOutputBlock lang="python"> ``` - > Entering new LLMChain chain... - Prompt after formatting: - 1 + 2 = - - > Finished chain. - +> Entering new LLMChain chain... +Prompt after formatting: +1 + 2 = - > Entering new LLMChain chain... - Prompt after formatting: - 1 + 2 = +> Finished chain. - > Finished chain. +> Entering new LLMChain chain... +Prompt after formatting: +1 + 2 = - > Entering new LLMChain chain... - Prompt after formatting: - 1 + 2 = +> Finished chain. - > Finished chain. +> Entering new LLMChain chain... +Prompt after formatting: +1 + 2 = - '\n\n3' +> Finished chain. ``` </CodeOutputBlock> From cfe95ab08521ddc01e9b65596ca50c9dba2d7677 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Mon, 22 Jan 2024 13:23:11 -0800 Subject: [PATCH 156/215] multiple: update langsmith dep (#16407) --- libs/community/poetry.lock | 4 +- libs/community/pyproject.toml | 2 +- libs/core/poetry.lock | 14 +---- libs/core/pyproject.toml | 2 +- libs/langchain/poetry.lock | 87 +++------------------------ libs/langchain/pyproject.toml | 2 +- libs/partners/robocorp/poetry.lock | 12 ++-- libs/partners/robocorp/pyproject.toml | 2 +- 8 files changed, 24 insertions(+), 101 deletions(-) diff --git a/libs/community/poetry.lock b/libs/community/poetry.lock index 3fdb4118bf872..cd2e0d3ae5e0c 100644 --- a/libs/community/poetry.lock +++ b/libs/community/poetry.lock @@ -3927,7 +3927,7 @@ develop = true [package.dependencies] anyio = ">=3,<5" jsonpatch = "^1.33" -langsmith = "^0.0.83" +langsmith = ">=0.0.83,<0.1" packaging = "^23.2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -9180,4 +9180,4 @@ extended-testing = ["aiosqlite", "aleph-alpha-client", "anthropic", "arxiv", "as [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "e512944a95e344bcf8b15066e289798c53fb39299f6e0190bf69371f43e6f63a" +content-hash = "73184aec5978e0de5b99029724164fa76394beb6359b59763ca488a258b0df4d" diff --git a/libs/community/pyproject.toml b/libs/community/pyproject.toml index 8528edbdb3ad9..465afacece680 100644 --- a/libs/community/pyproject.toml +++ b/libs/community/pyproject.toml @@ -17,7 +17,7 @@ numpy = "^1" aiohttp = "^3.8.3" tenacity = "^8.1.0" dataclasses-json = ">= 0.5.7, < 0.7" -langsmith = "~0.0.63" +langsmith = ">=0.0.83,<0.1" tqdm = {version = ">=4.48.0", optional = true} openapi-pydantic = {version = "^0.3.2", optional = true} faiss-cpu = {version = "^1", optional = true} diff --git a/libs/core/poetry.lock b/libs/core/poetry.lock index a800ee41841a3..8d8b5159d2e04 100644 --- a/libs/core/poetry.lock +++ b/libs/core/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -1943,7 +1943,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1951,15 +1950,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1976,7 +1968,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1984,7 +1975,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2755,4 +2745,4 @@ extended-testing = ["jinja2"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "57f3d2b399d31ffc19811c50c969c44cf7c072facbb8f48bc8aa9281a3f16c1b" +content-hash = "6cd163ca8c15acc3053e17fa86acce4c27c2d737ebcad633db93c0d7aa3a4a53" diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index c5eb14fbe2a6e..2a5ddba98ae94 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -11,7 +11,7 @@ repository = "https://github.com/langchain-ai/langchain" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" pydantic = ">=1,<3" -langsmith = "^0.0.83" +langsmith = ">=0.0.83,<0.1" tenacity = "^8.1.0" jsonpatch = "^1.33" anyio = ">=3,<5" diff --git a/libs/langchain/poetry.lock b/libs/langchain/poetry.lock index 9bf50ce8bf98c..9d7210910ef35 100644 --- a/libs/langchain/poetry.lock +++ b/libs/langchain/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiodns" @@ -2358,7 +2358,7 @@ files = [ {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, + {file = "greenlet-3.0.0-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:c3692ecf3fe754c8c0f2c95ff19626584459eab110eaab66413b1e7425cd84e9"}, {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, @@ -2368,6 +2368,7 @@ files = [ {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, + {file = "greenlet-3.0.0-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:553d6fb2324e7f4f0899e5ad2c427a4579ed4873f42124beba763f16032959af"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, @@ -3458,7 +3459,7 @@ develop = true aiohttp = "^3.8.3" dataclasses-json = ">= 0.5.7, < 0.7" langchain-core = ">=0.1.14,<0.2" -langsmith = "~0.0.63" +langsmith = ">=0.0.83,<0.1" numpy = "^1" PyYAML = ">=5.3" requests = "^2" @@ -3467,7 +3468,7 @@ tenacity = "^8.1.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] [package.source] type = "directory" @@ -3485,7 +3486,7 @@ develop = true [package.dependencies] anyio = ">=3,<5" jsonpatch = "^1.33" -langsmith = "^0.0.83" +langsmith = ">=0.0.83,<0.1" packaging = "^23.2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -3744,16 +3745,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -6323,7 +6314,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -6331,15 +6321,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -6356,7 +6339,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -6364,7 +6346,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -7572,54 +7553,6 @@ description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, - {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, ] @@ -7629,7 +7562,7 @@ typing-extensions = ">=4.2.0" [package.extras] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +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)"] @@ -7639,7 +7572,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] +oracle = ["cx_oracle (>=7)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -7649,7 +7582,7 @@ postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlite-vss" @@ -9128,4 +9061,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "9eb6114c56ea7772809c5cddd72986099861c63903eaed1649b205dbb662fa09" +content-hash = "2f5a1d207f8102e6531a0947886be63cd6d109dee4da9e37a0cb399bba259b4a" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index 455b0fbbfc4c9..7d370cfa05759 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -80,7 +80,7 @@ cassio = {version = "^0.1.0", optional = true} sympy = {version = "^1.12", optional = true} rapidfuzz = {version = "^3.1.1", optional = true} jsonschema = {version = ">1", optional = true} -langsmith = "^0.0.83" +langsmith = ">=0.0.83,<0.1" rank-bm25 = {version = "^0.2.2", optional = true} geopandas = {version = "^0.13.1", optional = true} gitpython = {version = "^3.1.32", optional = true} diff --git a/libs/partners/robocorp/poetry.lock b/libs/partners/robocorp/poetry.lock index a7ce2dbefcea1..468bfa069324c 100644 --- a/libs/partners/robocorp/poetry.lock +++ b/libs/partners/robocorp/poetry.lock @@ -251,7 +251,7 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.8" +version = "0.1.14" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -261,7 +261,7 @@ develop = true [package.dependencies] anyio = ">=3,<5" jsonpatch = "^1.33" -langsmith = "~0.0.63" +langsmith = ">=0.0.83,<0.1" packaging = "^23.2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -277,13 +277,13 @@ url = "../../core" [[package]] name = "langsmith" -version = "0.0.77" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.77-py3-none-any.whl", hash = "sha256:750c0aa9177240c64e131d831e009ed08dd59038f7cabbd0bbcf62ccb7c8dcac"}, - {file = "langsmith-0.0.77.tar.gz", hash = "sha256:c4c8d3a96ad8671a41064f3ccc673e2e22a4153e823b19f915c9c9b8a4f33a2c"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -839,4 +839,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "09efecb6b12a87a6f0662b33a341e069c09413fb35d99cc9b7da447bb2298bd2" +content-hash = "e64f42191e7c016d9d198a2b183d03fd245add9dc8e14632451e56bcf8a2aecd" diff --git a/libs/partners/robocorp/pyproject.toml b/libs/partners/robocorp/pyproject.toml index e8e7974146b09..add0f6d9cb490 100644 --- a/libs/partners/robocorp/pyproject.toml +++ b/libs/partners/robocorp/pyproject.toml @@ -15,7 +15,7 @@ python = ">=3.8.1,<4.0" langchain-core = ">=0.0.12" requests = "^2.31.0" types-requests = "^2.31.0.20231231" -langsmith = "^0.0.77" +langsmith = ">=0.0.83,<0.1" [tool.poetry.group.test] optional = true From 2ac3a82d856d573696f82e33c0b05383c111e10f Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Mon, 22 Jan 2024 13:26:47 -0800 Subject: [PATCH 157/215] cli[patch]: new fields in integration template, release 0.0.21 (#16398) --- libs/cli/langchain_cli/integration_template/pyproject.toml | 5 +++++ libs/cli/pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/cli/langchain_cli/integration_template/pyproject.toml b/libs/cli/langchain_cli/integration_template/pyproject.toml index 95d8fbcb1850b..b4703aa60e04c 100644 --- a/libs/cli/langchain_cli/integration_template/pyproject.toml +++ b/libs/cli/langchain_cli/integration_template/pyproject.toml @@ -4,6 +4,11 @@ version = "0.0.1" description = "An integration package connecting __ModuleName__ and LangChain" authors = [] readme = "README.md" +repository = "https://github.com/langchain-ai/langchain" +license = "MIT" + +[tool.poetry.urls] +"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/__package_name_short__" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 978a03052aa34..e927fcfbcdeb0 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-cli" -version = "0.0.20" +version = "0.0.21" description = "CLI for interacting with LangChain" authors = ["Erick Friis <erick@langchain.dev>"] license = "MIT" From 35ec0bbd3b5cfa092859daf5b1a5605371450a0a Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Mon, 22 Jan 2024 13:28:30 -0800 Subject: [PATCH 158/215] cli[patch]: pypi fields (#16410) --- libs/cli/pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index e927fcfbcdeb0..43b01fd8f5400 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -3,8 +3,12 @@ name = "langchain-cli" version = "0.0.21" description = "CLI for interacting with LangChain" authors = ["Erick Friis <erick@langchain.dev>"] -license = "MIT" readme = "README.md" +repository = "https://github.com/langchain-ai/langchain" +license = "MIT" + +[tool.poetry.urls] +"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/cli" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" From b9f5104e6cd94066c4e2e288742e4852a5408c59 Mon Sep 17 00:00:00 2001 From: Ian <ArGregoryIan@gmail.com> Date: Tue, 23 Jan 2024 05:56:56 +0800 Subject: [PATCH 159/215] communty[minor]: Store Message History to TiDB Database (#16304) This pull request integrates the TiDB database into LangChain for storing message history, marking one of several steps towards a comprehensive integration of TiDB with LangChain. A simple usage ```python from datetime import datetime from langchain_community.chat_message_histories import TiDBChatMessageHistory history = TiDBChatMessageHistory( connection_string="mysql+pymysql://<host>:<PASSWORD>@<host>:4000/<db>?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true", session_id="code_gen", earliest_time=datetime.utcnow(), # Optional to set earliest_time to load messages after this time point. ) history.add_user_message("hi! How's feature going?") history.add_ai_message("It's almot done") ``` --- .../memory/tidb_chat_message_history.ipynb | 77 +++++++++ .../chat_message_histories/__init__.py | 2 + .../chat_message_histories/tidb.py | 148 ++++++++++++++++++ .../chat_message_histories/test_tidb.py | 101 ++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 docs/docs/integrations/memory/tidb_chat_message_history.ipynb create mode 100644 libs/community/langchain_community/chat_message_histories/tidb.py create mode 100644 libs/community/tests/integration_tests/chat_message_histories/test_tidb.py diff --git a/docs/docs/integrations/memory/tidb_chat_message_history.ipynb b/docs/docs/integrations/memory/tidb_chat_message_history.ipynb new file mode 100644 index 0000000000000..8a49af973d8fc --- /dev/null +++ b/docs/docs/integrations/memory/tidb_chat_message_history.ipynb @@ -0,0 +1,77 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# TiDB\n", + "\n", + "> [TiDB](https://github.com/pingcap/tidb) is an open-source, cloud-native, distributed, MySQL-Compatible database for elastic scale and real-time analytics.\n", + "\n", + "This notebook introduces how to use TiDB to store chat message history. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime\n", + "\n", + "from langchain_community.chat_message_histories import TiDBChatMessageHistory\n", + "\n", + "history = TiDBChatMessageHistory(\n", + " connection_string=\"mysql+pymysql://<host>:<PASSWORD>@<host>:4000/<db>?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true\",\n", + " session_id=\"code_gen\",\n", + " earliest_time=datetime.utcnow(), # Optional to set earliest_time to load messages after this time point.\n", + ")\n", + "\n", + "history.add_user_message(\"hi! How's feature going?\")\n", + "history.add_ai_message(\"It's almot done\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[HumanMessage(content=\"hi! How's feature going?\"),\n", + " AIMessage(content=\"It's almot done\")]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "history.messages" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "langchain", + "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.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/libs/community/langchain_community/chat_message_histories/__init__.py b/libs/community/langchain_community/chat_message_histories/__init__.py index a45ecb7ead6e2..8803810c1bb7e 100644 --- a/libs/community/langchain_community/chat_message_histories/__init__.py +++ b/libs/community/langchain_community/chat_message_histories/__init__.py @@ -35,6 +35,7 @@ from langchain_community.chat_message_histories.streamlit import ( StreamlitChatMessageHistory, ) +from langchain_community.chat_message_histories.tidb import TiDBChatMessageHistory from langchain_community.chat_message_histories.upstash_redis import ( UpstashRedisChatMessageHistory, ) @@ -62,4 +63,5 @@ "ZepChatMessageHistory", "UpstashRedisChatMessageHistory", "Neo4jChatMessageHistory", + "TiDBChatMessageHistory", ] diff --git a/libs/community/langchain_community/chat_message_histories/tidb.py b/libs/community/langchain_community/chat_message_histories/tidb.py new file mode 100644 index 0000000000000..bfa36ad06ffa5 --- /dev/null +++ b/libs/community/langchain_community/chat_message_histories/tidb.py @@ -0,0 +1,148 @@ +import json +import logging +from datetime import datetime +from typing import List, Optional + +from langchain_core.chat_history import BaseChatMessageHistory +from langchain_core.messages import BaseMessage, message_to_dict, messages_from_dict +from sqlalchemy import create_engine, text +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import sessionmaker + +logger = logging.getLogger(__name__) + + +class TiDBChatMessageHistory(BaseChatMessageHistory): + """ + Represents a chat message history stored in a TiDB database. + """ + + def __init__( + self, + session_id: str, + connection_string: str, + table_name: str = "langchain_message_store", + earliest_time: Optional[datetime] = None, + ): + """ + Initializes a new instance of the TiDBChatMessageHistory class. + + Args: + session_id (str): The ID of the chat session. + connection_string (str): The connection string for the TiDB database. + format: mysql+pymysql://<host>:<PASSWORD>@<host>:4000/<db>?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true + table_name (str, optional): the table name to store the chat messages. + Defaults to "langchain_message_store". + earliest_time (Optional[datetime], optional): The earliest time to retrieve messages from. + Defaults to None. + """ # noqa + + self.session_id = session_id + self.table_name = table_name + self.earliest_time = earliest_time + self.cache = [] + + # Set up SQLAlchemy engine and session + self.engine = create_engine(connection_string) + Session = sessionmaker(bind=self.engine) + self.session = Session() + + self._create_table_if_not_exists() + self._load_messages_to_cache() + + def _create_table_if_not_exists(self) -> None: + """ + Creates a table if it does not already exist in the database. + """ + + create_table_query = text( + f""" + CREATE TABLE IF NOT EXISTS {self.table_name} ( + id INT AUTO_INCREMENT PRIMARY KEY, + session_id VARCHAR(255) NOT NULL, + message JSON NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX session_idx (session_id) + );""" + ) + try: + self.session.execute(create_table_query) + self.session.commit() + except SQLAlchemyError as e: + logger.error(f"Error creating table: {e}") + self.session.rollback() + + def _load_messages_to_cache(self) -> None: + """ + Loads messages from the database into the cache. + + This method retrieves messages from the database table. The retrieved messages + are then stored in the cache for faster access. + + Raises: + SQLAlchemyError: If there is an error executing the database query. + + """ + time_condition = ( + f"AND create_time >= '{self.earliest_time}'" if self.earliest_time else "" + ) + query = text( + f""" + SELECT message FROM {self.table_name} + WHERE session_id = :session_id {time_condition} + ORDER BY id; + """ + ) + try: + result = self.session.execute(query, {"session_id": self.session_id}) + for record in result.fetchall(): + message_dict = json.loads(record[0]) + self.cache.append(messages_from_dict([message_dict])[0]) + except SQLAlchemyError as e: + logger.error(f"Error loading messages to cache: {e}") + + @property + def messages(self) -> List[BaseMessage]: + """returns all messages""" + if len(self.cache) == 0: + self.reload_cache() + return self.cache + + def add_message(self, message: BaseMessage) -> None: + """adds a message to the database and cache""" + query = text( + f"INSERT INTO {self.table_name} (session_id, message) VALUES (:session_id, :message);" # noqa + ) + try: + self.session.execute( + query, + { + "session_id": self.session_id, + "message": json.dumps(message_to_dict(message)), + }, + ) + self.session.commit() + self.cache.append(message) + except SQLAlchemyError as e: + logger.error(f"Error adding message: {e}") + self.session.rollback() + + def clear(self) -> None: + """clears all messages""" + query = text(f"DELETE FROM {self.table_name} WHERE session_id = :session_id;") + try: + self.session.execute(query, {"session_id": self.session_id}) + self.session.commit() + self.cache.clear() + except SQLAlchemyError as e: + logger.error(f"Error clearing messages: {e}") + self.session.rollback() + + def reload_cache(self) -> None: + """reloads messages from database to cache""" + self.cache.clear() + self._load_messages_to_cache() + + def __del__(self) -> None: + """closes the session""" + self.session.close() diff --git a/libs/community/tests/integration_tests/chat_message_histories/test_tidb.py b/libs/community/tests/integration_tests/chat_message_histories/test_tidb.py new file mode 100644 index 0000000000000..17601af48b557 --- /dev/null +++ b/libs/community/tests/integration_tests/chat_message_histories/test_tidb.py @@ -0,0 +1,101 @@ +import os + +import pytest +from langchain_core.messages import AIMessage, HumanMessage + +from langchain_community.chat_message_histories import TiDBChatMessageHistory + +try: + CONNECTION_STRING = os.getenv("TEST_TiDB_CHAT_URL", "") + + if CONNECTION_STRING == "": + raise OSError("TEST_TiDB_URL environment variable is not set") + + tidb_available = True +except (OSError, ImportError): + tidb_available = False + + +@pytest.mark.skipif(not tidb_available, reason="tidb is not available") +def test_add_messages() -> None: + """Basic testing: adding messages to the TiDBChatMessageHistory.""" + message_store = TiDBChatMessageHistory("23334", CONNECTION_STRING) + message_store.clear() + assert len(message_store.messages) == 0 + message_store.add_user_message("Hello! Language Chain!") + message_store.add_ai_message("Hi Guys!") + + # create another message store to check if the messages are stored correctly + message_store_another = TiDBChatMessageHistory("46666", CONNECTION_STRING) + message_store_another.clear() + assert len(message_store_another.messages) == 0 + message_store_another.add_user_message("Hello! Bot!") + message_store_another.add_ai_message("Hi there!") + message_store_another.add_user_message("How's this pr going?") + + # Now check if the messages are stored in the database correctly + assert len(message_store.messages) == 2 + assert isinstance(message_store.messages[0], HumanMessage) + assert isinstance(message_store.messages[1], AIMessage) + assert message_store.messages[0].content == "Hello! Language Chain!" + assert message_store.messages[1].content == "Hi Guys!" + + assert len(message_store_another.messages) == 3 + assert isinstance(message_store_another.messages[0], HumanMessage) + assert isinstance(message_store_another.messages[1], AIMessage) + assert isinstance(message_store_another.messages[2], HumanMessage) + assert message_store_another.messages[0].content == "Hello! Bot!" + assert message_store_another.messages[1].content == "Hi there!" + assert message_store_another.messages[2].content == "How's this pr going?" + + # Now clear the first history + message_store.clear() + assert len(message_store.messages) == 0 + assert len(message_store_another.messages) == 3 + message_store_another.clear() + assert len(message_store.messages) == 0 + assert len(message_store_another.messages) == 0 + + +def test_tidb_recent_chat_message(): + """Test the TiDBChatMessageHistory with earliest_time parameter.""" + import time + from datetime import datetime + + # prepare some messages + message_store = TiDBChatMessageHistory("2333", CONNECTION_STRING) + message_store.clear() + assert len(message_store.messages) == 0 + message_store.add_user_message("Hello! Language Chain!") + message_store.add_ai_message("Hi Guys!") + assert len(message_store.messages) == 2 + assert isinstance(message_store.messages[0], HumanMessage) + assert isinstance(message_store.messages[1], AIMessage) + assert message_store.messages[0].content == "Hello! Language Chain!" + assert message_store.messages[1].content == "Hi Guys!" + + # now we add some recent messages to the database + earliest_time = datetime.utcnow() + time.sleep(1) + + message_store.add_user_message("How's this pr going?") + message_store.add_ai_message("It's almost done!") + assert len(message_store.messages) == 4 + assert isinstance(message_store.messages[2], HumanMessage) + assert isinstance(message_store.messages[3], AIMessage) + assert message_store.messages[2].content == "How's this pr going?" + assert message_store.messages[3].content == "It's almost done!" + + # now we create another message store with earliest_time parameter + message_store_another = TiDBChatMessageHistory( + "2333", CONNECTION_STRING, earliest_time=earliest_time + ) + assert len(message_store_another.messages) == 2 + assert isinstance(message_store_another.messages[0], HumanMessage) + assert isinstance(message_store_another.messages[1], AIMessage) + assert message_store_another.messages[0].content == "How's this pr going?" + assert message_store_another.messages[1].content == "It's almost done!" + + # now we clear the message store + message_store.clear() + assert len(message_store.messages) == 0 From 774e543e1f623c884a19f47152309e05f0f985a7 Mon Sep 17 00:00:00 2001 From: Jonathan Algar <93204286+jonathanalgar@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:59:45 +0000 Subject: [PATCH 160/215] docs: fix formatting issue in rockset.ipynb (#16328) **Description:** randomly discovered while working on another PR https://github.com/quarto-dev/quarto-cli/discussions/8131#discussioncomment-8027706 @anubhav94N ICYI --- docs/docs/integrations/vectorstores/rockset.ipynb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/docs/docs/integrations/vectorstores/rockset.ipynb b/docs/docs/integrations/vectorstores/rockset.ipynb index aa4645feec6ba..96620c0ef7859 100644 --- a/docs/docs/integrations/vectorstores/rockset.ipynb +++ b/docs/docs/integrations/vectorstores/rockset.ipynb @@ -17,7 +17,7 @@ "id": "b823d64a", "metadata": {}, "source": [ - "## Setting Up Your Environment[](https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/rockset#setting-up-environment)\n", + "## Setting Up Your Environment\n", "\n", "1. Leverage the `Rockset` console to create a [collection](https://rockset.com/docs/collections/) with the Write API as your source. In this walkthrough, we create a collection named `langchain_demo`. \n", " \n", @@ -249,14 +249,6 @@ "\n", "Keep an eye on https://rockset.com/ for future updates in this space." ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "054de494-e6c0-453a-becd-ebfb2fdf541a", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From d511366dd3d318801b306bc677e1dc9244f11d9b Mon Sep 17 00:00:00 2001 From: James Braza <jamesbraza@gmail.com> Date: Mon, 22 Jan 2024 14:00:23 -0800 Subject: [PATCH 161/215] infra: absolute `EXAMPLE_DIR` path in core unit tests (#16325) If you invoked testing from places besides `core/`, this `EXAMPLE_DIR` path won't work. This PR makes`EXAMPLE_DIR` robust against invocation location --- libs/core/tests/unit_tests/prompts/test_loading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/tests/unit_tests/prompts/test_loading.py b/libs/core/tests/unit_tests/prompts/test_loading.py index afad88766a836..59b5d95cb62ea 100644 --- a/libs/core/tests/unit_tests/prompts/test_loading.py +++ b/libs/core/tests/unit_tests/prompts/test_loading.py @@ -10,7 +10,7 @@ from langchain_core.prompts.loading import load_prompt from langchain_core.prompts.prompt import PromptTemplate -EXAMPLE_DIR = Path("tests/unit_tests/examples").absolute() +EXAMPLE_DIR = (Path(__file__).parent.parent / "examples").absolute() @contextmanager From fbe592a5ce10e69e3a2bd549cb067466356e4e6e Mon Sep 17 00:00:00 2001 From: s-g-1 <github@softwareanvil.co.uk> Date: Mon, 22 Jan 2024 23:01:33 +0100 Subject: [PATCH 162/215] community[patch]: fix typo in pgvecto_rs debug msg (#16318) fixes typo in pip install message for the pgvecto_rs community vector store no issues found mentioning this no dependents changed --- libs/community/langchain_community/vectorstores/pgvecto_rs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/vectorstores/pgvecto_rs.py b/libs/community/langchain_community/vectorstores/pgvecto_rs.py index 18d66c80a893a..2b14cd8036ea7 100644 --- a/libs/community/langchain_community/vectorstores/pgvecto_rs.py +++ b/libs/community/langchain_community/vectorstores/pgvecto_rs.py @@ -38,7 +38,7 @@ def __init__( except ImportError as e: raise ImportError( "Unable to import pgvector_rs.sdk , please install with " - '`pip install "pgvector_rs[sdk]"`.' + '`pip install "pgvecto_rs[sdk]"`.' ) from e self._store = PGVectoRs( db_url=db_url, From d1b4ead87c0aba59d91ed1742d27f49e541a04ef Mon Sep 17 00:00:00 2001 From: Alireza Kashani <alireza.kashanipour@gmail.com> Date: Mon, 22 Jan 2024 23:03:58 +0100 Subject: [PATCH 163/215] community[patch]: Update grobid.py (#16298) there is a case where "coords" does not exist in the "sentence" therefore, the "split(";")" will lead to error. we can fix that by adding "if sentence.get("coords") is not None:" the resulting empty "sbboxes" from this scenario will raise error at "sbboxes[0]["page"]" because sbboxes are empty. the PDF from https://pubmed.ncbi.nlm.nih.gov/23970373/ can replicate those errors. --- .../document_loaders/parsers/grobid.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/parsers/grobid.py b/libs/community/langchain_community/document_loaders/parsers/grobid.py index 8eb9974479c9d..f73f91150c2a2 100644 --- a/libs/community/langchain_community/document_loaders/parsers/grobid.py +++ b/libs/community/langchain_community/document_loaders/parsers/grobid.py @@ -59,19 +59,20 @@ def process_xml( for i, sentence in enumerate(paragraph.find_all("s")): paragraph_text.append(sentence.text) sbboxes = [] - for bbox in sentence.get("coords").split(";"): - box = bbox.split(",") - sbboxes.append( - { - "page": box[0], - "x": box[1], - "y": box[2], - "h": box[3], - "w": box[4], - } - ) - chunk_bboxes.append(sbboxes) - if segment_sentences is True: + if sentence.get("coords") is not None: + for bbox in sentence.get("coords").split(";"): + box = bbox.split(",") + sbboxes.append( + { + "page": box[0], + "x": box[1], + "y": box[2], + "h": box[3], + "w": box[4], + } + ) + chunk_bboxes.append(sbboxes) + if (segment_sentences is True) and (len(sbboxes) > 0): fpage, lpage = sbboxes[0]["page"], sbboxes[-1]["page"] sentence_dict = { "text": sentence.text, From 8da34118bc7c581e0d2562b5dce308ecf6a37179 Mon Sep 17 00:00:00 2001 From: Christophe Bornet <cbornet@hotmail.com> Date: Mon, 22 Jan 2024 23:06:21 +0100 Subject: [PATCH 164/215] docs: Add documentation for Cassandra Document Loader (#16282) --- .../document_loaders/cassandra.ipynb | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 docs/docs/integrations/document_loaders/cassandra.ipynb diff --git a/docs/docs/integrations/document_loaders/cassandra.ipynb b/docs/docs/integrations/document_loaders/cassandra.ipynb new file mode 100644 index 0000000000000..49f261a18a84b --- /dev/null +++ b/docs/docs/integrations/document_loaders/cassandra.ipynb @@ -0,0 +1,241 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "id": "vm8vn9t8DvC_" + }, + "source": [ + "# Cassandra" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Cassandra](https://cassandra.apache.org/) is a NoSQL, row-oriented, highly scalable and highly available database.Starting with version 5.0, the database ships with [vector search capabilities](https://cassandra.apache.org/doc/trunk/cassandra/vector-search/overview.html)." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "id": "5WjXERXzFEhg" + }, + "source": [ + "## Overview" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "id": "juAmbgoWD17u" + }, + "source": [ + "The Cassandra Document Loader returns a list of Langchain Documents from a Cassandra database.\n", + "\n", + "You must either provide a CQL query or a table name to retrieve the documents.\n", + "The Loader takes the following parameters:\n", + "\n", + "* table: (Optional) The table to load the data from.\n", + "* session: (Optional) The cassandra driver session. If not provided, the cassio resolved session will be used.\n", + "* keyspace: (Optional) The keyspace of the table. If not provided, the cassio resolved keyspace will be used.\n", + "* query: (Optional) The query used to load the data.\n", + "* page_content_mapper: (Optional) a function to convert a row to string page content. The default converts the row to JSON.\n", + "* metadata_mapper: (Optional) a function to convert a row to metadata dict.\n", + "* query_parameters: (Optional) The query parameters used when calling session.execute .\n", + "* query_timeout: (Optional) The query timeout used when calling session.execute .\n", + "* query_custom_payload: (Optional) The query custom_payload used when calling `session.execute`.\n", + "* query_execution_profile: (Optional) The query execution_profile used when calling `session.execute`.\n", + "* query_host: (Optional) The query host used when calling `session.execute`.\n", + "* query_execute_as: (Optional) The query execute_as used when calling `session.execute`." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load documents with the Document Loader" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.document_loaders import CassandraLoader" + ] + }, + { + "cell_type": "markdown", + "source": [ + "### Init from a cassandra driver Session\n", + "\n", + "You need to create a `cassandra.cluster.Session` object, as described in the [Cassandra driver documentation](https://docs.datastax.com/en/developer/python-driver/latest/api/cassandra/cluster/#module-cassandra.cluster). The details vary (e.g. with network settings and authentication), but this might be something like:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "from cassandra.cluster import Cluster\n", + "\n", + "cluster = Cluster()\n", + "session = cluster.connect()" + ], + "metadata": { + "collapsed": false + }, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "You need to provide the name of an existing keyspace of the Cassandra instance:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "CASSANDRA_KEYSPACE = input(\"CASSANDRA_KEYSPACE = \")" + ], + "metadata": { + "collapsed": false + }, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Creating the document loader:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T15:47:25.893037Z", + "start_time": "2024-01-19T15:47:25.889398Z" + } + }, + "outputs": [], + "source": [ + "loader = CassandraLoader(\n", + " table=\"movie_reviews\",\n", + " session=session,\n", + " keyspace=CASSANDRA_KEYSPACE,\n", + ")" + ] + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "docs = loader.load()" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-01-19T15:47:26.399472Z", + "start_time": "2024-01-19T15:47:26.389145Z" + } + }, + "execution_count": 17 + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T15:47:33.287783Z", + "start_time": "2024-01-19T15:47:33.277862Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": "Document(page_content='Row(_id=\\'659bdffa16cbc4586b11a423\\', title=\\'Dangerous Men\\', reviewtext=\\'\"Dangerous Men,\" the picture\\\\\\'s production notes inform, took 26 years to reach the big screen. After having seen it, I wonder: What was the rush?\\')', metadata={'table': 'movie_reviews', 'keyspace': 'default_keyspace'})" + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docs[0]" + ] + }, + { + "cell_type": "markdown", + "source": [ + "### Init from cassio\n", + "\n", + "It's also possible to use cassio to configure the session and keyspace." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "import cassio\n", + "\n", + "cassio.init(contact_points=\"127.0.0.1\", keyspace=CASSANDRA_KEYSPACE)\n", + "\n", + "loader = CassandraLoader(\n", + " table=\"movie_reviews\",\n", + ")\n", + "\n", + "docs = loader.load()" + ], + "metadata": { + "collapsed": false + }, + "execution_count": null + } + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "5WjXERXzFEhg" + ], + "provenance": [] + }, + "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.18" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From b26a22f30749b060c5af503895709021ff8d7de1 Mon Sep 17 00:00:00 2001 From: parkererickson-tg <117180142+parkererickson-tg@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:07:44 -0600 Subject: [PATCH 165/215] community[minor]: add TigerGraph support (#16280) **Description:** Add support for querying TigerGraph databases through the InquiryAI service. **Issue**: N/A **Dependencies:** N/A **Twitter handle:** @TigerGraphDB --- .../integrations/providers/tigergraph.mdx | 34 +++++++ .../langchain_community/graphs/__init__.py | 2 + .../graphs/tigergraph_graph.py | 94 +++++++++++++++++++ .../tests/unit_tests/graphs/test_imports.py | 1 + 4 files changed, 131 insertions(+) create mode 100644 docs/docs/integrations/providers/tigergraph.mdx create mode 100644 libs/community/langchain_community/graphs/tigergraph_graph.py diff --git a/docs/docs/integrations/providers/tigergraph.mdx b/docs/docs/integrations/providers/tigergraph.mdx new file mode 100644 index 0000000000000..d637d0f3dbc9e --- /dev/null +++ b/docs/docs/integrations/providers/tigergraph.mdx @@ -0,0 +1,34 @@ +# TigerGraph + +This page covers how to use the TigerGraph ecosystem within LangChain. + +What is TigerGraph? + +**TigerGraph in a nutshell:** + +- TigerGraph is a natively distributed and high-performance graph database. +- The storage of data in a graph format of vertices and edges leads to rich relationships, ideal for grouding LLM responses. +- Get started quickly with TigerGraph by visiting [their website](https://tigergraph.com/). + +## Installation and Setup + +- Install the Python SDK with `pip install pyTigerGraph` + +## Wrappers + +### TigerGraph Store +To utilize the TigerGraph InquiryAI functionality, you can import `TigerGraph` from `langchain_community.graphs`. + +```python +import pyTigerGraph as tg +conn = tg.TigerGraphConnection(host="DATABASE_HOST_HERE", graphname="GRAPH_NAME_HERE", username="USERNAME_HERE", password="PASSWORD_HERE") + +### ==== CONFIGURE INQUIRYAI HOST ==== +conn.ai.configureInquiryAIHost("INQUIRYAI_HOST_HERE") + +from langchain_community.graphs import TigerGraph +graph = TigerGraph(conn) +result = graph.query("How many servers are there?") +print(result) +``` + diff --git a/libs/community/langchain_community/graphs/__init__.py b/libs/community/langchain_community/graphs/__init__.py index 7de3bdbc7bda4..4037f1572143f 100644 --- a/libs/community/langchain_community/graphs/__init__.py +++ b/libs/community/langchain_community/graphs/__init__.py @@ -10,6 +10,7 @@ from langchain_community.graphs.neptune_graph import NeptuneGraph from langchain_community.graphs.networkx_graph import NetworkxEntityGraph from langchain_community.graphs.rdf_graph import RdfGraph +from langchain_community.graphs.tigergraph_graph import TigerGraph __all__ = [ "MemgraphGraph", @@ -22,4 +23,5 @@ "RdfGraph", "ArangoGraph", "FalkorDBGraph", + "TigerGraph", ] diff --git a/libs/community/langchain_community/graphs/tigergraph_graph.py b/libs/community/langchain_community/graphs/tigergraph_graph.py new file mode 100644 index 0000000000000..cff2f4e2ce7fa --- /dev/null +++ b/libs/community/langchain_community/graphs/tigergraph_graph.py @@ -0,0 +1,94 @@ +from typing import Any, Dict, List, Optional + +from langchain_community.graphs.graph_store import GraphStore + + +class TigerGraph(GraphStore): + """TigerGraph wrapper for graph operations. + + *Security note*: Make sure that the database connection uses credentials + that are narrowly-scoped to only include necessary permissions. + Failure to do so may result in data corruption or loss, since the calling + code may attempt commands that would result in deletion, mutation + of data if appropriately prompted or reading sensitive data if such + data is present in the database. + The best way to guard against such negative outcomes is to (as appropriate) + limit the permissions granted to the credentials used with this tool. + + See https://python.langchain.com/docs/security for more information. + """ + + def __init__(self, conn: Any) -> None: + """Create a new TigerGraph graph wrapper instance.""" + self.set_connection(conn) + self.set_schema() + + @property + def conn(self) -> Any: + return self._conn + + @property + def schema(self) -> Dict[str, Any]: + return self._schema + + def get_schema(self) -> str: + if self._schema: + return str(self._schema) + else: + self.set_schema() + return str(self._schema) + + def set_connection(self, conn: Any) -> None: + from pyTigerGraph import TigerGraphConnection + + if not isinstance(conn, TigerGraphConnection): + msg = "**conn** parameter must inherit from TigerGraphConnection" + raise TypeError(msg) + + if conn.ai.nlqs_host is None: + msg = """**conn** parameter does not have nlqs_host parameter defined. + Define hostname of NLQS service.""" + raise ConnectionError(msg) + + self._conn: TigerGraphConnection = conn + self.set_schema() + + def set_schema(self, schema: Optional[Dict[str, Any]] = None) -> None: + """ + Set the schema of the TigerGraph Database. + Auto-generates Schema if **schema** is None. + """ + self._schema = self.generate_schema() if schema is None else schema + + def generate_schema( + self, + ) -> Dict[str, List[Dict[str, Any]]]: + """ + Generates the schema of the TigerGraph Database and returns it + User can specify a **sample_ratio** (0 to 1) to determine the + ratio of documents/edges used (in relation to the Collection size) + to render each Collection Schema. + """ + return self._conn.getSchema(force=True) + + def refresh_schema(self): + self.generate_schema() + + def query(self, query: str) -> Dict[str, Any]: + """Query the TigerGraph database.""" + answer = self._conn.ai.query(query) + return answer + + def register_query( + self, + function_header: str, + description: str, + docstring: str, + param_types: dict = {}, + ) -> List[str]: + """ + Wrapper function to register a custom GSQL query to the TigerGraph NLQS. + """ + return self._conn.ai.registerCustomQuery( + function_header, description, docstring, param_types + ) diff --git a/libs/community/tests/unit_tests/graphs/test_imports.py b/libs/community/tests/unit_tests/graphs/test_imports.py index c50dfe769b0b9..fb98e973d0792 100644 --- a/libs/community/tests/unit_tests/graphs/test_imports.py +++ b/libs/community/tests/unit_tests/graphs/test_imports.py @@ -11,6 +11,7 @@ "RdfGraph", "ArangoGraph", "FalkorDBGraph", + "TigerGraph", ] From 1011b681dc54f87c45e186ab40999f556750fa5b Mon Sep 17 00:00:00 2001 From: Chase VanSteenburg <cvansteenburg@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:08:44 -0800 Subject: [PATCH 166/215] core[patch]: Fix f-string formatting in error message for configurable_fields (#16411) - **Description:** Simple fix to f-string formatting. Allows more informative ValueError output. - **Issue:** None needed. - **Dependencies:** None. - **Twitter handle:** @FlightP1an --- libs/core/langchain_core/runnables/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index cdaab4015b5d1..280ecd25eb3f4 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -1646,7 +1646,7 @@ def configurable_fields( if key not in self.__fields__: raise ValueError( f"Configuration key {key} not found in {self}: " - "available keys are {self.__fields__.keys()}" + f"available keys are {self.__fields__.keys()}" ) return RunnableConfigurableFields(default=self, fields=kwargs) From a950fa0487728281a1b6a6f84beb33cb7ae55bfa Mon Sep 17 00:00:00 2001 From: ChengZi <chen.zhang@zilliz.com> Date: Tue, 23 Jan 2024 06:25:26 +0800 Subject: [PATCH 167/215] docs: add milvus multitenancy doc (#16177) - **Description:** add milvus multitenancy doc, it is an example for this [pr](https://github.com/langchain-ai/langchain/pull/15740) . - **Issue:** No, - **Dependencies:** No, - **Twitter handle:** No Signed-off-by: ChengZi <chen.zhang@zilliz.com> --- .../integrations/vectorstores/milvus.ipynb | 116 +++++++++++++++++- .../question_answering/per_user.ipynb | 14 ++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/docs/docs/integrations/vectorstores/milvus.ipynb b/docs/docs/integrations/vectorstores/milvus.ipynb index 1d3a25f684c6c..79d0df5c3de52 100644 --- a/docs/docs/integrations/vectorstores/milvus.ipynb +++ b/docs/docs/integrations/vectorstores/milvus.ipynb @@ -201,6 +201,120 @@ "source": [ "After retreival you can go on querying it as usual." ] + }, + { + "cell_type": "markdown", + "source": [ + "### Per-User Retrieval\n", + "\n", + "When building a retrieval app, you often have to build it with multiple users in mind. This means that you may be storing data not just for one user, but for many different users, and they should not be able to see eachother’s data.\n", + "\n", + "Milvus recommends using [partition_key](https://milvus.io/docs/multi_tenancy.md#Partition-key-based-multi-tenancy) to implement multi-tenancy, here is an example." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [], + "source": [ + "from langchain_core.documents import Document\n", + "\n", + "docs = [\n", + " Document(page_content=\"i worked at kensho\", metadata={\"namespace\": \"harrison\"}),\n", + " Document(page_content=\"i worked at facebook\", metadata={\"namespace\": \"ankush\"}),\n", + "]\n", + "vectorstore = Milvus.from_documents(\n", + " docs,\n", + " embeddings,\n", + " connection_args={\"host\": \"127.0.0.1\", \"port\": \"19530\"},\n", + " drop_old=True,\n", + " partition_key_field=\"namespace\", # Use the \"namespace\" field as the partition key\n", + ")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "To conduct a search using the partition key, you should include either of the following in the boolean expression of the search request:\n", + "\n", + "`search_kwargs={\"expr\": '<partition_key> == \"xxxx\"'}`\n", + "\n", + "`search_kwargs={\"expr\": '<partition_key> == in [\"xxx\", \"xxx\"]'}`\n", + "\n", + "Do replace `<partition_key>` with the name of the field that is designated as the partition key.\n", + "\n", + "Milvus changes to a partition based on the specified partition key, filters entities according to the partition key, and searches among the filtered entities.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "data": { + "text/plain": "[Document(page_content='i worked at facebook', metadata={'namespace': 'ankush'})]" + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This will only get documents for Ankush\n", + "vectorstore.as_retriever(\n", + " search_kwargs={\"expr\": 'namespace == \"ankush\"'}\n", + ").get_relevant_documents(\"where did i work?\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 4, + "outputs": [ + { + "data": { + "text/plain": "[Document(page_content='i worked at kensho', metadata={'namespace': 'harrison'})]" + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This will only get documents for Harrison\n", + "vectorstore.as_retriever(\n", + " search_kwargs={\"expr\": 'namespace == \"harrison\"'}\n", + ").get_relevant_documents(\"where did i work?\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } } ], "metadata": { @@ -224,4 +338,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/docs/use_cases/question_answering/per_user.ipynb b/docs/docs/use_cases/question_answering/per_user.ipynb index 3724d006751ce..d1459a73919ba 100644 --- a/docs/docs/use_cases/question_answering/per_user.ipynb +++ b/docs/docs/use_cases/question_answering/per_user.ipynb @@ -298,6 +298,18 @@ " config={\"configurable\": {\"search_kwargs\": {\"namespace\": \"ankush\"}}},\n", ")" ] + }, + { + "cell_type": "markdown", + "source": [ + "For more vectorstore implementations for multi-user, please refer to specific pages, such as [Milvus](/docs/integrations/vectorstores/milvus)." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } } ], "metadata": { @@ -321,4 +333,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From 5694728816ae9b8c93b420dbc2fd6172cbae85a5 Mon Sep 17 00:00:00 2001 From: Frank995 <47689966+Frank995@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:32:44 +0100 Subject: [PATCH 168/215] community[patch]: Implement vector length definition at init time in PGVector for indexing (#16133) Replace this entire comment with: - **Description:** allow user to define tVector length in PGVector when creating the embedding store, this allows for later indexing - **Issue:** #16132 - **Dependencies:** None --- .../langchain_community/vectorstores/pgvector.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/pgvector.py b/libs/community/langchain_community/vectorstores/pgvector.py index eff9fac35406b..fe439d86c5a7e 100644 --- a/libs/community/langchain_community/vectorstores/pgvector.py +++ b/libs/community/langchain_community/vectorstores/pgvector.py @@ -62,7 +62,7 @@ class BaseModel(Base): _classes: Any = None -def _get_embedding_collection_store() -> Any: +def _get_embedding_collection_store(vector_dimension: Optional[int] = None) -> Any: global _classes if _classes is not None: return _classes @@ -125,7 +125,7 @@ class EmbeddingStore(BaseModel): ) collection = relationship(CollectionStore, back_populates="embeddings") - embedding: Vector = sqlalchemy.Column(Vector(None)) + embedding: Vector = sqlalchemy.Column(Vector(vector_dimension)) document = sqlalchemy.Column(sqlalchemy.String, nullable=True) cmetadata = sqlalchemy.Column(JSON, nullable=True) @@ -151,6 +151,10 @@ class PGVector(VectorStore): connection_string: Postgres connection string. embedding_function: Any embedding function implementing `langchain.embeddings.base.Embeddings` interface. + embedding_length: The length of the embedding vector. (default: None) + NOTE: This is not mandatory. Defining it will prevent vectors of + any other size to be added to the embeddings table but, without it, + the embeddings can't be indexed. collection_name: The name of the collection to use. (default: langchain) NOTE: This is not the name of the table, but the name of the collection. The tables will be created when initializing the store (if not exists) @@ -183,6 +187,7 @@ def __init__( self, connection_string: str, embedding_function: Embeddings, + embedding_length: Optional[int] = None, collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME, collection_metadata: Optional[dict] = None, distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, @@ -195,6 +200,7 @@ def __init__( ) -> None: self.connection_string = connection_string self.embedding_function = embedding_function + self._embedding_length = embedding_length self.collection_name = collection_name self.collection_metadata = collection_metadata self._distance_strategy = distance_strategy @@ -211,7 +217,9 @@ def __post_init__( """Initialize the store.""" self.create_vector_extension() - EmbeddingStore, CollectionStore = _get_embedding_collection_store() + EmbeddingStore, CollectionStore = _get_embedding_collection_store( + self._embedding_length + ) self.CollectionStore = CollectionStore self.EmbeddingStore = EmbeddingStore self.create_tables_if_not_exists() From d6275e47f2d308107561053fff8396be07b44924 Mon Sep 17 00:00:00 2001 From: Jennifer Melot <jtmelot@gmail.com> Date: Mon, 22 Jan 2024 17:34:22 -0500 Subject: [PATCH 169/215] docs: Updated integration docs structure for tools/arxiv (#16091) (#16250) - **Description:** Updated docs for tools/arxiv to use `AgentExecutor` and `invoke` - **Issue:** #15664 - **Dependencies:** None - **Twitter handle:** None --- docs/docs/integrations/tools/arxiv.ipynb | 54 ++++++++++-------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/docs/docs/integrations/tools/arxiv.ipynb b/docs/docs/integrations/tools/arxiv.ipynb index ec5dd30d37f1f..74b5b134f76fd 100644 --- a/docs/docs/integrations/tools/arxiv.ipynb +++ b/docs/docs/integrations/tools/arxiv.ipynb @@ -9,7 +9,7 @@ "\n", "This notebook goes over how to use the `arxiv` tool with an agent. \n", "\n", - "First, you need to install `arxiv` python package." + "First, you need to install the `arxiv` python package." ] }, { @@ -36,20 +36,18 @@ }, "outputs": [], "source": [ - "from langchain.agents import AgentType, initialize_agent, load_tools\n", + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_react_agent, load_tools\n", "from langchain_openai import ChatOpenAI\n", "\n", "llm = ChatOpenAI(temperature=0.0)\n", "tools = load_tools(\n", " [\"arxiv\"],\n", ")\n", + "prompt = hub.pull(\"hwchase17/react\")\n", "\n", - "agent_chain = initialize_agent(\n", - " tools,\n", - " llm,\n", - " agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,\n", - " verbose=True,\n", - ")" + "agent = create_react_agent(llm, tools, prompt)\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)" ] }, { @@ -67,10 +65,9 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mI need to use Arxiv to search for the paper.\n", - "Action: Arxiv\n", - "Action Input: \"1605.08386\"\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mPublished: 2016-05-26\n", + "\u001b[32;1m\u001b[1;3mI should use the arxiv tool to search for the paper with the given identifier.\n", + "Action: arxiv\n", + "Action Input: 1605.08386\u001b[0m\u001b[36;1m\u001b[1;3mPublished: 2016-05-26\n", "Title: Heat-bath random walks with Markov bases\n", "Authors: Caprice Stanley, Tobias Windisch\n", "Summary: Graphs on lattice points are studied whose edges come from a finite set of\n", @@ -79,18 +76,15 @@ "then study the mixing behaviour of heat-bath random walks on these graphs. We\n", "also state explicit conditions on the set of moves so that the heat-bath random\n", "walk, a generalization of the Glauber dynamics, is an expander in fixed\n", - "dimension.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3mThe paper is about heat-bath random walks with Markov bases on graphs of lattice points.\n", - "Final Answer: The paper 1605.08386 is about heat-bath random walks with Markov bases on graphs of lattice points.\u001b[0m\n", + "dimension.\u001b[0m\u001b[32;1m\u001b[1;3mThe paper \"1605.08386\" is titled \"Heat-bath random walks with Markov bases\" and is authored by Caprice Stanley and Tobias Windisch. It was published on May 26, 2016. The paper discusses the study of graphs on lattice points with edges coming from a finite set of allowed moves. It explores the diameter of these graphs and the mixing behavior of heat-bath random walks on them. The paper also discusses conditions for the heat-bath random walk to be an expander in fixed dimension.\n", + "Final Answer: The paper \"1605.08386\" is about heat-bath random walks with Markov bases.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] }, { "data": { - "text/plain": [ - "'The paper 1605.08386 is about heat-bath random walks with Markov bases on graphs of lattice points.'" - ] + "text/plain": "{'input': \"What's the paper 1605.08386 about?\",\n 'output': 'The paper \"1605.08386\" is about heat-bath random walks with Markov bases.'}" }, "execution_count": 3, "metadata": {}, @@ -98,8 +92,10 @@ } ], "source": [ - "agent_chain.run(\n", - " \"What's the paper 1605.08386 about?\",\n", + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"What's the paper 1605.08386 about?\",\n", + " }\n", ")" ] }, @@ -130,15 +126,15 @@ "id": "c89c110c-96ac-4fe1-ba3e-6056543d1a59", "metadata": {}, "source": [ - "Run a query to get information about some `scientific article`/articles. The query text is limited to 300 characters.\n", + "You can use the ArxivAPIWrapper to get information about a scientific article or articles. The query text is limited to 300 characters.\n", "\n", - "It returns these article fields:\n", + "The ArxivAPIWrapper returns these article fields:\n", "- Publishing date\n", "- Title\n", "- Authors\n", "- Summary\n", "\n", - "Next query returns information about one article with arxiv Id equal \"1605.08386\". " + "The following query returns information about one article with the arxiv ID \"1605.08386\". " ] }, { @@ -151,9 +147,7 @@ "outputs": [ { "data": { - "text/plain": [ - "'Published: 2016-05-26\\nTitle: Heat-bath random walks with Markov bases\\nAuthors: Caprice Stanley, Tobias Windisch\\nSummary: Graphs on lattice points are studied whose edges come from a finite set of\\nallowed moves of arbitrary length. We show that the diameter of these graphs on\\nfibers of a fixed integer matrix can be bounded from above by a constant. We\\nthen study the mixing behaviour of heat-bath random walks on these graphs. We\\nalso state explicit conditions on the set of moves so that the heat-bath random\\nwalk, a generalization of the Glauber dynamics, is an expander in fixed\\ndimension.'" - ] + "text/plain": "'Published: 2016-05-26\\nTitle: Heat-bath random walks with Markov bases\\nAuthors: Caprice Stanley, Tobias Windisch\\nSummary: Graphs on lattice points are studied whose edges come from a finite set of\\nallowed moves of arbitrary length. We show that the diameter of these graphs on\\nfibers of a fixed integer matrix can be bounded from above by a constant. We\\nthen study the mixing behaviour of heat-bath random walks on these graphs. We\\nalso state explicit conditions on the set of moves so that the heat-bath random\\nwalk, a generalization of the Glauber dynamics, is an expander in fixed\\ndimension.'" }, "execution_count": 5, "metadata": {}, @@ -186,9 +180,7 @@ "outputs": [ { "data": { - "text/plain": [ - "'Published: 2017-10-10\\nTitle: On Mixing Behavior of a Family of Random Walks Determined by a Linear Recurrence\\nAuthors: Caprice Stanley, Seth Sullivant\\nSummary: We study random walks on the integers mod $G_n$ that are determined by an\\ninteger sequence $\\\\{ G_n \\\\}_{n \\\\geq 1}$ generated by a linear recurrence\\nrelation. Fourier analysis provides explicit formulas to compute the\\neigenvalues of the transition matrices and we use this to bound the mixing time\\nof the random walks.\\n\\nPublished: 2016-05-26\\nTitle: Heat-bath random walks with Markov bases\\nAuthors: Caprice Stanley, Tobias Windisch\\nSummary: Graphs on lattice points are studied whose edges come from a finite set of\\nallowed moves of arbitrary length. We show that the diameter of these graphs on\\nfibers of a fixed integer matrix can be bounded from above by a constant. We\\nthen study the mixing behaviour of heat-bath random walks on these graphs. We\\nalso state explicit conditions on the set of moves so that the heat-bath random\\nwalk, a generalization of the Glauber dynamics, is an expander in fixed\\ndimension.\\n\\nPublished: 2003-03-18\\nTitle: Calculation of fluxes of charged particles and neutrinos from atmospheric showers\\nAuthors: V. Plyaskin\\nSummary: The results on the fluxes of charged particles and neutrinos from a\\n3-dimensional (3D) simulation of atmospheric showers are presented. An\\nagreement of calculated fluxes with data on charged particles from the AMS and\\nCAPRICE detectors is demonstrated. Predictions on neutrino fluxes at different\\nexperimental sites are compared with results from other calculations.'" - ] + "text/plain": "'Published: 2017-10-10\\nTitle: On Mixing Behavior of a Family of Random Walks Determined by a Linear Recurrence\\nAuthors: Caprice Stanley, Seth Sullivant\\nSummary: We study random walks on the integers mod $G_n$ that are determined by an\\ninteger sequence $\\\\{ G_n \\\\}_{n \\\\geq 1}$ generated by a linear recurrence\\nrelation. Fourier analysis provides explicit formulas to compute the\\neigenvalues of the transition matrices and we use this to bound the mixing time\\nof the random walks.\\n\\nPublished: 2016-05-26\\nTitle: Heat-bath random walks with Markov bases\\nAuthors: Caprice Stanley, Tobias Windisch\\nSummary: Graphs on lattice points are studied whose edges come from a finite set of\\nallowed moves of arbitrary length. We show that the diameter of these graphs on\\nfibers of a fixed integer matrix can be bounded from above by a constant. We\\nthen study the mixing behaviour of heat-bath random walks on these graphs. We\\nalso state explicit conditions on the set of moves so that the heat-bath random\\nwalk, a generalization of the Glauber dynamics, is an expander in fixed\\ndimension.\\n\\nPublished: 2003-03-18\\nTitle: Calculation of fluxes of charged particles and neutrinos from atmospheric showers\\nAuthors: V. Plyaskin\\nSummary: The results on the fluxes of charged particles and neutrinos from a\\n3-dimensional (3D) simulation of atmospheric showers are presented. An\\nagreement of calculated fluxes with data on charged particles from the AMS and\\nCAPRICE detectors is demonstrated. Predictions on neutrino fluxes at different\\nexperimental sites are compared with results from other calculations.'" }, "execution_count": 6, "metadata": {}, @@ -218,9 +210,7 @@ "outputs": [ { "data": { - "text/plain": [ - "'No good Arxiv Result was found'" - ] + "text/plain": "'No good Arxiv Result was found'" }, "execution_count": 7, "metadata": {}, From b9e7f6f38a4a40c301bc11aff4fea464661693a0 Mon Sep 17 00:00:00 2001 From: DL <66421606+DLOVRIC2@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:44:49 +0100 Subject: [PATCH 170/215] community[minor]: Bedrock async methods (#12477) Description: Added support for asynchronous streaming in the Bedrock class and corresponding tests. Primarily: async def aprepare_output_stream async def _aprepare_input_and_invoke_stream async def _astream async def _acall I've ensured that the code adheres to the project's linting and formatting standards by running make format, make lint, and make test. Issue: #12054, #11589 Dependencies: None Tag maintainer: @baskaryan Twitter handle: @dominic_lovric --------- Co-authored-by: Piyush Jain <piyushjain@duck.com> --- .../langchain_community/llms/bedrock.py | 180 ++++++++++++++++-- .../tests/unit_tests/llms/test_bedrock.py | 58 +++++- 2 files changed, 221 insertions(+), 17 deletions(-) diff --git a/libs/community/langchain_community/llms/bedrock.py b/libs/community/langchain_community/llms/bedrock.py index 3f10b09b63e37..5ec60e84967c3 100644 --- a/libs/community/langchain_community/llms/bedrock.py +++ b/libs/community/langchain_community/llms/bedrock.py @@ -1,11 +1,25 @@ from __future__ import annotations +import asyncio import json import warnings from abc import ABC -from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Mapping, Optional +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + AsyncIterator, + Dict, + Iterator, + List, + Mapping, + Optional, +) -from langchain_core.callbacks import CallbackManagerForLLMRun +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) from langchain_core.language_models.llms import LLM from langchain_core.outputs import GenerationChunk from langchain_core.pydantic_v1 import BaseModel, Extra, Field, root_validator @@ -128,26 +142,56 @@ def prepare_output_stream( if not stream: return - if provider not in cls.provider_to_output_key_map: + output_key = cls.provider_to_output_key_map.get(provider, None) + + if not output_key: raise ValueError( f"Unknown streaming response output key for provider: {provider}" ) for event in stream: chunk = event.get("chunk") - if chunk: - chunk_obj = json.loads(chunk.get("bytes").decode()) - if provider == "cohere" and ( - chunk_obj["is_finished"] - or chunk_obj[cls.provider_to_output_key_map[provider]] - == "<EOS_TOKEN>" - ): - return - - # chunk obj format varies with provider - yield GenerationChunk( - text=chunk_obj[cls.provider_to_output_key_map[provider]] - ) + if not chunk: + continue + + chunk_obj = json.loads(chunk.get("bytes").decode()) + + if provider == "cohere" and ( + chunk_obj["is_finished"] or chunk_obj[output_key] == "<EOS_TOKEN>" + ): + return + + yield GenerationChunk(text=chunk_obj[output_key]) + + @classmethod + async def aprepare_output_stream( + cls, provider: str, response: Any, stop: Optional[List[str]] = None + ) -> AsyncIterator[GenerationChunk]: + stream = response.get("body") + + if not stream: + return + + output_key = cls.provider_to_output_key_map.get(provider, None) + + if not output_key: + raise ValueError( + f"Unknown streaming response output key for provider: {provider}" + ) + + for event in stream: + chunk = event.get("chunk") + if not chunk: + continue + + chunk_obj = json.loads(chunk.get("bytes").decode()) + + if provider == "cohere" and ( + chunk_obj["is_finished"] or chunk_obj[output_key] == "<EOS_TOKEN>" + ): + return + + yield GenerationChunk(text=chunk_obj[output_key]) class BedrockBase(BaseModel, ABC): @@ -332,6 +376,51 @@ def _prepare_input_and_invoke_stream( if run_manager is not None: run_manager.on_llm_new_token(chunk.text, chunk=chunk) + async def _aprepare_input_and_invoke_stream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> AsyncIterator[GenerationChunk]: + _model_kwargs = self.model_kwargs or {} + provider = self._get_provider() + + if stop: + if provider not in self.provider_stop_sequence_key_name_map: + raise ValueError( + f"Stop sequence key name for {provider} is not supported." + ) + _model_kwargs[self.provider_stop_sequence_key_name_map.get(provider)] = stop + + if provider == "cohere": + _model_kwargs["stream"] = True + + params = {**_model_kwargs, **kwargs} + input_body = LLMInputOutputAdapter.prepare_input(provider, prompt, params) + body = json.dumps(input_body) + + response = await asyncio.get_running_loop().run_in_executor( + None, + lambda: self.client.invoke_model_with_response_stream( + body=body, + modelId=self.model_id, + accept="application/json", + contentType="application/json", + ), + ) + + async for chunk in LLMInputOutputAdapter.aprepare_output_stream( + provider, response, stop + ): + yield chunk + if run_manager is not None and asyncio.iscoroutinefunction( + run_manager.on_llm_new_token + ): + await run_manager.on_llm_new_token(chunk.text, chunk=chunk) + elif run_manager is not None: + run_manager.on_llm_new_token(chunk.text, chunk=chunk) + class Bedrock(LLM, BedrockBase): """Bedrock models. @@ -449,6 +538,65 @@ def _call( return self._prepare_input_and_invoke(prompt=prompt, stop=stop, **kwargs) + async def _astream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> AsyncGenerator[GenerationChunk, None]: + """Call out to Bedrock service with streaming. + + Args: + prompt (str): The prompt to pass into the model + stop (Optional[List[str]], optional): Stop sequences. These will + override any stop sequences in the `model_kwargs` attribute. + Defaults to None. + run_manager (Optional[CallbackManagerForLLMRun], optional): Callback + run managers used to process the output. Defaults to None. + + Yields: + AsyncGenerator[GenerationChunk, None]: Generator that asynchronously yields + the streamed responses. + """ + async for chunk in self._aprepare_input_and_invoke_stream( + prompt=prompt, stop=stop, run_manager=run_manager, **kwargs + ): + yield chunk + + async def _acall( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + """Call out to Bedrock service model. + + Args: + prompt: The prompt to pass into the model. + stop: Optional list of stop words to use when generating. + + Returns: + The string generated by the model. + + Example: + .. code-block:: python + + response = await llm._acall("Tell me a joke.") + """ + + if not self.streaming: + raise ValueError("Streaming must be set to True for async operations. ") + + chunks = [ + chunk.text + async for chunk in self._astream( + prompt=prompt, stop=stop, run_manager=run_manager, **kwargs + ) + ] + return "".join(chunks) + def get_num_tokens(self, text: str) -> int: if self._model_is_anthropic: return get_num_tokens_anthropic(text) diff --git a/libs/community/tests/unit_tests/llms/test_bedrock.py b/libs/community/tests/unit_tests/llms/test_bedrock.py index 12467b4f42a43..60012182936cc 100644 --- a/libs/community/tests/unit_tests/llms/test_bedrock.py +++ b/libs/community/tests/unit_tests/llms/test_bedrock.py @@ -1,6 +1,14 @@ +import json +from typing import AsyncGenerator, Dict +from unittest.mock import MagicMock, patch + import pytest -from langchain_community.llms.bedrock import ALTERNATION_ERROR, _human_assistant_format +from langchain_community.llms.bedrock import ( + ALTERNATION_ERROR, + Bedrock, + _human_assistant_format, +) TEST_CASES = { """Hey""": """ @@ -250,3 +258,51 @@ def test__human_assistant_format() -> None: else: output = _human_assistant_format(input_text) assert output == expected_output + + +# Sample mock streaming response data +MOCK_STREAMING_RESPONSE = [ + {"chunk": {"bytes": b'{"text": "nice"}'}}, + {"chunk": {"bytes": b'{"text": " to meet"}'}}, + {"chunk": {"bytes": b'{"text": " you"}'}}, +] + + +async def async_gen_mock_streaming_response() -> AsyncGenerator[Dict, None]: + for item in MOCK_STREAMING_RESPONSE: + yield item + + +@pytest.mark.asyncio +async def test_bedrock_async_streaming_call() -> None: + # Mock boto3 import + mock_boto3 = MagicMock() + mock_boto3.Session.return_value.client.return_value = ( + MagicMock() + ) # Mocking the client method of the Session object + + with patch.dict( + "sys.modules", {"boto3": mock_boto3} + ): # Mocking boto3 at the top level using patch.dict + # Mock the `Bedrock` class's method that invokes the model + mock_invoke_method = MagicMock(return_value=async_gen_mock_streaming_response()) + with patch.object( + Bedrock, "_aprepare_input_and_invoke_stream", mock_invoke_method + ): + # Instantiate the Bedrock LLM + llm = Bedrock( + client=None, + model_id="anthropic.claude-v2", + streaming=True, + ) + # Call the _astream method + chunks = [ + json.loads(chunk["chunk"]["bytes"])["text"] # type: ignore + async for chunk in llm._astream("Hey, how are you?") + ] + + # Assertions + assert len(chunks) == 3 + assert chunks[0] == "nice" + assert chunks[1] == " to meet" + assert chunks[2] == " you" From a500527030c136aaf7727a7579cb57f8c6dbfa2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= <boschi1997@gmail.com> Date: Mon, 22 Jan 2024 23:54:41 +0100 Subject: [PATCH 171/215] infra: google-vertexai relax types-requests deps range (#16264) - **Description:** At the moment it's not possible to include in the same project langchain-google-vertexai and boto3 (e.g. use bedrock and vertex in the same application) because of the dependency resolutions conflict. boto3 is still using urllib3 1.x, meanwhile langchain-google-vertexai -> types-requests depends on urllib3 2.x. [the last version of types-requests that allows urllib3 1.x is 2.31.0.6](https://pypi.org/project/types-requests/#description). In this PR I allow the vertexai package to get that version also. - **Twitter handle:** nicoloboschi --- libs/partners/google-vertexai/poetry.lock | 14 ++++++++++++-- libs/partners/google-vertexai/pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/libs/partners/google-vertexai/poetry.lock b/libs/partners/google-vertexai/poetry.lock index 48233dce17e2c..929bb2ea8b881 100644 --- a/libs/partners/google-vertexai/poetry.lock +++ b/libs/partners/google-vertexai/poetry.lock @@ -1084,7 +1084,7 @@ extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15. [[package]] name = "langchain-core" -version = "0.1.11" +version = "0.1.12" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -1701,6 +1701,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1708,8 +1709,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1726,6 +1734,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1733,6 +1742,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2254,4 +2264,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "f86059ed812a97d68a0a9715c2f6d9f7687b5c07685ae224a63ff35f06e55a70" +content-hash = "a031be3cb062d347bc7b9e1ec95f37c5e0a5184f46c935cc77fbeac9b64bad62" diff --git a/libs/partners/google-vertexai/pyproject.toml b/libs/partners/google-vertexai/pyproject.toml index 41bcffb357144..a6e3bbab48f1c 100644 --- a/libs/partners/google-vertexai/pyproject.toml +++ b/libs/partners/google-vertexai/pyproject.toml @@ -15,7 +15,7 @@ python = ">=3.8.1,<4.0" langchain-core = ">=0.1.7,<0.2" google-cloud-aiplatform = "^1.39.0" google-cloud-storage = "^2.14.0" -types-requests = "^2.31.0.20231231" +types-requests = "^2.31.0" types-protobuf = "^4.24.0.4" [tool.poetry.group.test] From 404abf139a24a7e519bd98f18f2298e206ae0624 Mon Sep 17 00:00:00 2001 From: Boris Feld <lothiraldan@gmail.com> Date: Tue, 23 Jan 2024 00:17:16 +0100 Subject: [PATCH 172/215] community: Add CometLLM tracing context var (#15765) I also added LANGCHAIN_COMET_TRACING to enable the CometLLM tracing integration similar to other tracing integrations. This is easier for end-users to enable it rather than importing the callback and pass it manually. (This is the same content as https://github.com/langchain-ai/langchain/pull/14650 but rebased and squashed as something seems to confuse Github Action). --- .../callbacks/comet_tracing.ipynb | 138 ++++++++++++++++++ .../langchain_community/callbacks/manager.py | 7 + 2 files changed, 145 insertions(+) create mode 100644 docs/docs/integrations/callbacks/comet_tracing.ipynb diff --git a/docs/docs/integrations/callbacks/comet_tracing.ipynb b/docs/docs/integrations/callbacks/comet_tracing.ipynb new file mode 100644 index 0000000000000..aff2be5610275 --- /dev/null +++ b/docs/docs/integrations/callbacks/comet_tracing.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5371a9bb", + "metadata": {}, + "source": [ + "# Comet Tracing\n", + "\n", + "There are two ways to trace your LangChains executions with Comet:\n", + "\n", + "1. Setting the `LANGCHAIN_COMET_TRACING` environment variable to \"true\". This is the recommended way.\n", + "2. Import the `CometTracer` manually and pass it explicitely." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17c04cc6-c93d-4b6c-a033-e897577f4ed1", + "metadata": { + "ExecuteTime": { + "end_time": "2023-05-18T12:47:46.580776Z", + "start_time": "2023-05-18T12:47:46.577833Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import comet_llm\n", + "\n", + "os.environ[\"LANGCHAIN_COMET_TRACING\"] = \"true\"\n", + "\n", + "# Connect to Comet if no API Key is set\n", + "comet_llm.init()\n", + "\n", + "# comet documentation to configure comet using env variables\n", + "# https://www.comet.com/docs/v2/api-and-sdk/llm-sdk/configuration/\n", + "# here we are configuring the comet project\n", + "os.environ[\"COMET_PROJECT_NAME\"] = \"comet-example-langchain-tracing\"\n", + "\n", + "from langchain.agents import AgentType, initialize_agent, load_tools\n", + "from langchain.llms import OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b62cd48", + "metadata": { + "ExecuteTime": { + "end_time": "2023-05-18T12:47:47.445229Z", + "start_time": "2023-05-18T12:47:47.436424Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Agent run with tracing. Ensure that OPENAI_API_KEY is set appropriately to run this example.\n", + "\n", + "llm = OpenAI(temperature=0)\n", + "tools = load_tools([\"llm-math\"], llm=llm)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfa16b79-aa4b-4d41-a067-70d1f593f667", + "metadata": { + "ExecuteTime": { + "end_time": "2023-05-18T12:48:01.816137Z", + "start_time": "2023-05-18T12:47:49.109574Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "agent = initialize_agent(\n", + " tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n", + ")\n", + "\n", + "agent.run(\"What is 2 raised to .123243 power?\") # this should be traced\n", + "# An url for the chain like the following should print in your console:\n", + "# https://www.comet.com/<workspace>/<project_name>\n", + "# The url can be used to view the LLM chain in Comet." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e212e7d", + "metadata": {}, + "outputs": [], + "source": [ + "# Now, we unset the environment variable and use a context manager.\n", + "if \"LANGCHAIN_COMET_TRACING\" in os.environ:\n", + " del os.environ[\"LANGCHAIN_COMET_TRACING\"]\n", + "\n", + "from langchain.callbacks.tracers.comet import CometTracer\n", + "\n", + "tracer = CometTracer()\n", + "\n", + "# Recreate the LLM, tools and agent and passing the callback to each of them\n", + "llm = OpenAI(temperature=0)\n", + "tools = load_tools([\"llm-math\"], llm=llm)\n", + "agent = initialize_agent(\n", + " tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n", + ")\n", + "\n", + "agent.run(\n", + " \"What is 2 raised to .123243 power?\", callbacks=[tracer]\n", + ") # this should be traced" + ] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/community/langchain_community/callbacks/manager.py b/libs/community/langchain_community/callbacks/manager.py index 196afed3d0fe5..ec03a8234530c 100644 --- a/libs/community/langchain_community/callbacks/manager.py +++ b/libs/community/langchain_community/callbacks/manager.py @@ -11,6 +11,7 @@ from langchain_core.tracers.context import register_configure_hook from langchain_community.callbacks.openai_info import OpenAICallbackHandler +from langchain_community.callbacks.tracers.comet import CometTracer from langchain_community.callbacks.tracers.wandb import WandbTracer logger = logging.getLogger(__name__) @@ -21,11 +22,17 @@ wandb_tracing_callback_var: ContextVar[Optional[WandbTracer]] = ContextVar( # noqa: E501 "tracing_wandb_callback", default=None ) +comet_tracing_callback_var: ContextVar[Optional[CometTracer]] = ContextVar( # noqa: E501 + "tracing_comet_callback", default=None +) register_configure_hook(openai_callback_var, True) register_configure_hook( wandb_tracing_callback_var, True, WandbTracer, "LANGCHAIN_WANDB_TRACING" ) +register_configure_hook( + comet_tracing_callback_var, True, CometTracer, "LANGCHAIN_COMET_TRACING" +) @contextmanager From e5672bc94445c79a01cd4793b8436cd777154d42 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Mon, 22 Jan 2024 20:28:31 -0500 Subject: [PATCH 173/215] docs: Re-write custom agent to show to write a tools agent (#15907) Shows how to write a tools agent rather than a functions agent. --- .../modules/agents/how_to/custom_agent.ipynb | 103 +++++++++++------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/docs/docs/modules/agents/how_to/custom_agent.ipynb b/docs/docs/modules/agents/how_to/custom_agent.ipynb index 665db6168319e..2bd19eba28202 100644 --- a/docs/docs/modules/agents/how_to/custom_agent.ipynb +++ b/docs/docs/modules/agents/how_to/custom_agent.ipynb @@ -19,7 +19,7 @@ "\n", "This notebook goes through how to create your own custom agent.\n", "\n", - "In this example, we will use OpenAI Function Calling to create this agent.\n", + "In this example, we will use OpenAI Tool Calling to create this agent.\n", "**This is generally the most reliable way to create agents.**\n", "\n", "We will first create it WITHOUT memory, but we will then show how to add memory in.\n", @@ -61,10 +61,21 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "fbe32b5f", + "execution_count": 2, + "id": "490bab35-adbb-4b45-8d0d-232414121e97", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from langchain.agents import tool\n", "\n", @@ -75,6 +86,16 @@ " return len(word)\n", "\n", "\n", + "get_word_length.invoke(\"abc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c9821fb3-4449-49a0-a708-88a18d39e068", + "metadata": {}, + "outputs": [], + "source": [ "tools = [get_word_length]" ] }, @@ -91,7 +112,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "id": "aa4b50ea", "metadata": {}, "outputs": [], @@ -116,22 +137,24 @@ "metadata": {}, "source": [ "## Bind tools to LLM\n", + "\n", "How does the agent know what tools it can use?\n", - "In this case we're relying on OpenAI function calling LLMs, which take functions as a separate argument and have been specifically trained to know when to invoke those functions.\n", "\n", - "To pass in our tools to the agent, we just need to format them to the [OpenAI function format](https://openai.com/blog/function-calling-and-other-api-updates) and pass them to our model. (By `bind`-ing the functions, we're making sure that they're passed in each time the model is invoked.)" + "In this case we're relying on OpenAI tool calling LLMs, which take tools as a separate argument and have been specifically trained to know when to invoke those tools.\n", + "\n", + "To pass in our tools to the agent, we just need to format them to the [OpenAI tool format](https://platform.openai.com/docs/api-reference/chat/create) and pass them to our model. (By `bind`-ing the functions, we're making sure that they're passed in each time the model is invoked.)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "e82713b6", "metadata": {}, "outputs": [], "source": [ - "from langchain_community.tools.convert_to_openai import format_tool_to_openai_function\n", + "from langchain_community.tools.convert_to_openai import format_tool_to_openai_tool\n", "\n", - "llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])" + "llm_with_tools = llm.bind(tools=[format_tool_to_openai_tool(tool) for tool in tools])" ] }, { @@ -146,30 +169,32 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "925a8ca4", "metadata": {}, "outputs": [], "source": [ - "from langchain.agents.format_scratchpad import format_to_openai_function_messages\n", - "from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser\n", + "from langchain.agents.format_scratchpad.openai_tools import (\n", + " format_to_openai_tool_messages,\n", + ")\n", + "from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser\n", "\n", "agent = (\n", " {\n", " \"input\": lambda x: x[\"input\"],\n", - " \"agent_scratchpad\": lambda x: format_to_openai_function_messages(\n", + " \"agent_scratchpad\": lambda x: format_to_openai_tool_messages(\n", " x[\"intermediate_steps\"]\n", " ),\n", " }\n", " | prompt\n", " | llm_with_tools\n", - " | OpenAIFunctionsAgentOutputParser()\n", + " | OpenAIToolsAgentOutputParser()\n", ")" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "9af9734e", "metadata": {}, "outputs": [], @@ -181,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 8, "id": "653b1617", "metadata": {}, "outputs": [ @@ -193,10 +218,10 @@ "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3m\n", - "Invoking: `get_word_length` with `{'word': 'educa'}`\n", + "Invoking: `get_word_length` with `{'word': 'eudca'}`\n", "\n", "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3m5\u001b[0m\u001b[32;1m\u001b[1;3mThere are 5 letters in the word \"educa\".\u001b[0m\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m5\u001b[0m\u001b[32;1m\u001b[1;3mThere are 5 letters in the word \"eudca\".\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -204,17 +229,21 @@ { "data": { "text/plain": [ - "{'input': 'How many letters in the word educa',\n", - " 'output': 'There are 5 letters in the word \"educa\".'}" + "[{'actions': [OpenAIToolAgentAction(tool='get_word_length', tool_input={'word': 'eudca'}, log=\"\\nInvoking: `get_word_length` with `{'word': 'eudca'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_U9SR78eT398r9UbzID2N9LXh', 'function': {'arguments': '{\\n \"word\": \"eudca\"\\n}', 'name': 'get_word_length'}, 'type': 'function'}]})], tool_call_id='call_U9SR78eT398r9UbzID2N9LXh')],\n", + " 'messages': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_U9SR78eT398r9UbzID2N9LXh', 'function': {'arguments': '{\\n \"word\": \"eudca\"\\n}', 'name': 'get_word_length'}, 'type': 'function'}]})]},\n", + " {'steps': [AgentStep(action=OpenAIToolAgentAction(tool='get_word_length', tool_input={'word': 'eudca'}, log=\"\\nInvoking: `get_word_length` with `{'word': 'eudca'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_U9SR78eT398r9UbzID2N9LXh', 'function': {'arguments': '{\\n \"word\": \"eudca\"\\n}', 'name': 'get_word_length'}, 'type': 'function'}]})], tool_call_id='call_U9SR78eT398r9UbzID2N9LXh'), observation=5)],\n", + " 'messages': [FunctionMessage(content='5', name='get_word_length')]},\n", + " {'output': 'There are 5 letters in the word \"eudca\".',\n", + " 'messages': [AIMessage(content='There are 5 letters in the word \"eudca\".')]}]" ] }, - "execution_count": 14, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "agent_executor.invoke({\"input\": \"How many letters in the word educa\"})" + "list(agent_executor.stream({\"input\": \"How many letters in the word eudca\"}))" ] }, { @@ -227,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 9, "id": "60f5dc19", "metadata": {}, "outputs": [ @@ -237,7 +266,7 @@ "AIMessage(content='There are 6 letters in the word \"educa\".')" ] }, - "execution_count": 15, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -270,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "id": "169006d5", "metadata": {}, "outputs": [], @@ -301,7 +330,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 11, "id": "8c03f36c", "metadata": {}, "outputs": [], @@ -321,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 12, "id": "5429d97f", "metadata": {}, "outputs": [], @@ -329,14 +358,14 @@ "agent = (\n", " {\n", " \"input\": lambda x: x[\"input\"],\n", - " \"agent_scratchpad\": lambda x: format_to_openai_function_messages(\n", + " \"agent_scratchpad\": lambda x: format_to_openai_tool_messages(\n", " x[\"intermediate_steps\"]\n", " ),\n", " \"chat_history\": lambda x: x[\"chat_history\"],\n", " }\n", " | prompt\n", " | llm_with_tools\n", - " | OpenAIFunctionsAgentOutputParser()\n", + " | OpenAIToolsAgentOutputParser()\n", ")\n", "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)" ] @@ -351,7 +380,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 13, "id": "9d9da346", "metadata": {}, "outputs": [ @@ -386,7 +415,7 @@ " 'output': 'No, \"educa\" is not a real word in English.'}" ] }, - "execution_count": 19, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -402,14 +431,6 @@ ")\n", "agent_executor.invoke({\"input\": \"is that a real word?\", \"chat_history\": chat_history})" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f21bcd99", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -428,7 +449,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.11.4" }, "vscode": { "interpreter": { From c88750d54b0faf7a3a1250bb9be093dbfa74b9fc Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Mon, 22 Jan 2024 21:54:55 -0500 Subject: [PATCH 174/215] Docs: Agent streaming notebooks (#15858) Update information about streaming in the agents section. Show how to use astream_events to get token by token streaming. --- .../modules/agents/how_to/streaming.ipynb | 1680 +++++++++-------- .../agents/how_to/streaming_events.ipynb | 350 ---- 2 files changed, 862 insertions(+), 1168 deletions(-) delete mode 100644 docs/docs/modules/agents/how_to/streaming_events.ipynb diff --git a/docs/docs/modules/agents/how_to/streaming.ipynb b/docs/docs/modules/agents/how_to/streaming.ipynb index deb3e35891bf2..b6095de0a468a 100644 --- a/docs/docs/modules/agents/how_to/streaming.ipynb +++ b/docs/docs/modules/agents/how_to/streaming.ipynb @@ -17,65 +17,163 @@ "source": [ "# Streaming\n", "\n", - "Streaming is an important UX consideration for LLM apps, and agents are no exception. Streaming with agents is made more complicated by the fact that it's not just tokens that you will want to stream, but you may also want to stream back the intermediate steps an agent takes.\n", + "Streaming is an important UX consideration for LLM apps, and agents are no exception. Streaming with agents is made more complicated by the fact that it's not just tokens of the final answer that you will want to stream, but you may also want to stream back the intermediate steps an agent takes.\n", "\n", - "Let's take a look at how to do this." + "In this notebook, we'll cover the `stream/astream` and `astream_events` for streaming.\n", + "\n", + "Our agent will use a tools API for tool invocation with the tools:\n", + "\n", + "1. `where_cat_is_hiding`: Returns a location where the cat is hiding\n", + "2. `get_items`: Lists items that can be found in a particular place\n", + "\n", + "These tools will allow us to explore streaming in a more interesting situation where the agent will have to use both tools to answer some questions (e.g., to answer the question `what items are located where the cat is hiding?`).\n", + "\n", + "Ready?🏎️" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d40aae3d-b872-4e0f-ad54-8df6150fa863", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_openai_tools_agent\n", + "from langchain.prompts import ChatPromptTemplate\n", + "from langchain.tools import tool\n", + "from langchain_core.callbacks import Callbacks\n", + "from langchain_openai import ChatOpenAI" ] }, { "cell_type": "markdown", - "id": "def159c3", + "id": "59502ed8-2f9f-4758-a0d5-90a0392ed33d", "metadata": {}, "source": [ - "## Set up the agent\n", - "\n", - "Let's set up a simple agent for demonstration purposes. For our tool, we will use [Tavily](/docs/integrations/tools/tavily_search). Make sure that you've exported an API key with \n", + "## Create the model\n", "\n", - "```bash\n", - "export TAVILY_API_KEY=\"...\"\n", - "```" + "**Attention** We're setting `streaming=True` on the LLM. This will allow us to stream tokens from the agent using the `astream_events` API. This is needed for older versions of LangChain." ] }, { "cell_type": "code", - "execution_count": 1, - "id": "670078c4", + "execution_count": 2, + "id": "66e36d43-2c12-4cda-b591-383eb61b4f69", "metadata": {}, "outputs": [], "source": [ - "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "\n", - "search = TavilySearchResults()\n", - "tools = [search]" + "model = ChatOpenAI(temperature=0, streaming=True)" ] }, { "cell_type": "markdown", - "id": "5e04164b", + "id": "7ec9c5e5-34d4-4208-9f78-7f9a1ff3029b", "metadata": {}, "source": [ - "We will use a prompt from the hub - you can inspect the prompt more at [https://smith.langchain.com/hub/hwchase17/openai-functions-agent](https://smith.langchain.com/hub/hwchase17/openai-functions-agent)" + "## Tools\n", + "\n", + "We define two tools that rely on a chat model to generate output!" ] }, { "cell_type": "code", "execution_count": 3, - "id": "d8c5d907", + "id": "cd29a18c-e11c-4fbe-9fb8-b64dc9be95fd", "metadata": {}, "outputs": [], "source": [ - "from langchain import hub\n", - "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", - "from langchain_openai import ChatOpenAI\n", + "import random\n", "\n", - "# Get the prompt to use - you can modify this!\n", - "# If you want to see the prompt in full, you can at: https://smith.langchain.com/hub/hwchase17/openai-functions-agent\n", - "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n", "\n", - "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "@tool\n", + "async def where_cat_is_hiding() -> str:\n", + " \"\"\"Where is the cat hiding right now?\"\"\"\n", + " return random.choice([\"under the bed\", \"on the shelf\"])\n", "\n", - "agent = create_openai_functions_agent(llm, tools, prompt)\n", - "agent_executor = AgentExecutor(agent=agent, tools=tools)" + "\n", + "@tool\n", + "async def get_items(place: str) -> str:\n", + " \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n", + " if \"bed\" in place: # For under the bed\n", + " return \"socks, shoes and dust bunnies\"\n", + " if \"shelf\" in place: # For 'shelf'\n", + " return \"books, penciles and pictures\"\n", + " else: # if the agent decides to ask about a different place\n", + " return \"cat snacks\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1257a508-c791-4d81-82d2-df021c560bec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'on the shelf'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await where_cat_is_hiding.ainvoke({})" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "eea408ee-5260-418c-b769-5ba20e2999e1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'books, penciles and pictures'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await get_items.ainvoke({\"place\": \"shelf\"})" + ] + }, + { + "cell_type": "markdown", + "id": "07c08cd5-34eb-41a7-b524-7c3d1d274a67", + "metadata": {}, + "source": [ + "## Initialize the agent\n", + "\n", + "Here, we'll initialize an OpenAI tools agent.\n", + "\n", + "**ATTENTION** Please note that we associated the name `Agent` with our agent using `\"run_name\"=\"Agent\"`. We'll use that fact later on with the `astream_events` API." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "adecca7a-9864-496d-a3a9-906b56ecd03b", + "metadata": {}, + "outputs": [], + "source": [ + "# Get the prompt to use - you can modify this!\n", + "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", + "# print(prompt.messages) -- to see the prompt\n", + "tools = [get_items, where_cat_is_hiding]\n", + "agent = create_openai_tools_agent(\n", + " model.with_config({\"tags\": [\"agent_llm\"]}), tools, prompt\n", + ")\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools).with_config(\n", + " {\"run_name\": \"Agent\"}\n", + ")" ] }, { @@ -83,51 +181,132 @@ "id": "cba9a9eb", "metadata": {}, "source": [ - "## Stream intermediate steps\n", + "## Stream Intermediate Steps\n", + "\n", + "We'll use `.stream` method of the AgentExecutor to stream the agent's intermediate steps.\n", + "\n", + "The output from `.stream` alternates between (action, observation) pairs, finally concluding with the answer if the agent achieved its objective. \n", + "\n", + "It'll look like this:\n", + "\n", + "1. actions output\n", + "2. observations output\n", + "3. actions output\n", + "4. observations output\n", + "\n", + "**... (continue until goal is reached) ...**\n", + "\n", + "Then, if the final goal is reached, the agent will output the **final answer**.\n", "\n", - "Let's look at how to stream intermediate steps. We can do this easily by just using the `.stream` method on the AgentExecutor" + "\n", + "The contents of these outputs are summarized here:\n", + "\n", + "| Output | Contents |\n", + "|----------------------|------------------------------------------------------------------------------------------------------|\n", + "| **Actions** | `actions` `AgentAction` or a subclass, `messages` chat messages corresponding to action invocation |\n", + "| **Observations** | `steps` History of what the agent did so far, including the current action and its observation, `messages` chat message with function invocation results (aka observations)|\n", + "| **Final answer** | `output` `AgentFinish`, `messages` chat messages with the final output|" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "b6bd9bf2", + "execution_count": 7, + "id": "eab4d4a0-55ed-407a-baf0-9f0eaf8c3518", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'actions': [AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})])], 'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", "------\n", - "{'steps': [AgentStep(action=AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]), observation=[{'url': 'https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US', 'content': 'recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...'}])], 'messages': [FunctionMessage(content='[{\"url\": \"https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US\", \"content\": \"recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...\"}]', name='tavily_search_results_json')]}\n", + "{'actions': [...], 'messages': [...]}\n", "------\n", - "{'actions': [AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in Los Angeles'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in Los Angeles'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in Los Angeles\"\\n}', 'name': 'tavily_search_results_json'}})])], 'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in Los Angeles\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [...], 'steps': [...]}\n", "------\n", - "{'steps': [AgentStep(action=AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in Los Angeles'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in Los Angeles'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in Los Angeles\"\\n}', 'name': 'tavily_search_results_json'}})]), observation=[{'url': 'https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2', 'content': 'recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Air Quality Alert Today Tue 26 | Day Considerable cloudiness with occasional rain showers. High 64F. Winds light and variable. Chance of rain 50%. Considerable cloudiness with occasional rain showers. High 62F. Winds light and variable. Chance of rain 60%. Wed 03 Wed 03 | Day Overcast with showers at times. High 66F. Winds light and variable. Chance of rain 40%.Today 66°/ 50° 6% Sun 24 | Day 66° 6% WSW 4 mph Partly cloudy skies. High 66F. Winds light and variable. Humidity 65% UV Index 3 of 11 Sunrise 6:56 am Sunset 4:49 pm Sun 24 | Night 50° 10% N 1 mph...'}, {'url': 'https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=d4a04df2f5cd7d6ef329c49238253e994619763fd5f77a424ca3f1af9957e717', 'content': 'recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Air Quality Alert Today Tue 26 | Day Rain showers early with some sunshine later in the day. High 61F. Winds SSE at 5 to 10 mph. Chance of rain 60%. Thu 04 Thu 04 | Day Overcast with rain showers at times. High 63F. Winds S at 5 to 10 mph. Chance of rain 50%. Wed 03 Wed 03 | Day Cloudy with occasional showers. High 63F. Winds SE at 5 to 10 mph. Chance of rain 50%.10 Day Weather - Los Angeles, CA As of 12:06 am PST Tonight --/ 49° 5% Sat 23 | Night 49° 5% NNW 2 mph Partly cloudy this evening, then becoming foggy and damp after midnight. Low 49F. Winds...'}, {'url': 'https://weather.com/weather/hourbyhour/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2', 'content': 'recents Specialty Forecasts Hourly Weather-Los Angeles, CA Air Quality Alert Tuesday, December 26 10 am Partly Cloudy Cold & Flu Forecast Flu risk is low in your area 3 pm Partly Cloudy 4 pm Mostly Cloudy 5 pm Mostly Cloudy 6 pm Mostly Cloudy 7 pm Cloudy 8 pm Cloudy 9 pm Mostly Cloudy 10 am Cloudy 11 am Mostly Cloudy 12 pm Cloudy 1 pm Mostly Cloudy 2 pm Cloudy 3 pm Cloudy 4 pm Cloudy 5 pm Cloudy 6 pmHourly Weather Forecast for Los Angeles, CA - The Weather Channel | Weather.com - Los Angeles, CA As of 11:12 am PST Saturday, December 23 12 pm 64° 4% Mostly Cloudy Feels Like 64°...'}])], 'messages': [FunctionMessage(content='[{\"url\": \"https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2\", \"content\": \"recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Air Quality Alert Today Tue 26 | Day Considerable cloudiness with occasional rain showers. High 64F. Winds light and variable. Chance of rain 50%. Considerable cloudiness with occasional rain showers. High 62F. Winds light and variable. Chance of rain 60%. Wed 03 Wed 03 | Day Overcast with showers at times. High 66F. Winds light and variable. Chance of rain 40%.Today 66°/ 50° 6% Sun 24 | Day 66° 6% WSW 4 mph Partly cloudy skies. High 66F. Winds light and variable. Humidity 65% UV Index 3 of 11 Sunrise 6:56 am Sunset 4:49 pm Sun 24 | Night 50° 10% N 1 mph...\"}, {\"url\": \"https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=d4a04df2f5cd7d6ef329c49238253e994619763fd5f77a424ca3f1af9957e717\", \"content\": \"recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Air Quality Alert Today Tue 26 | Day Rain showers early with some sunshine later in the day. High 61F. Winds SSE at 5 to 10 mph. Chance of rain 60%. Thu 04 Thu 04 | Day Overcast with rain showers at times. High 63F. Winds S at 5 to 10 mph. Chance of rain 50%. Wed 03 Wed 03 | Day Cloudy with occasional showers. High 63F. Winds SE at 5 to 10 mph. Chance of rain 50%.10 Day Weather - Los Angeles, CA As of 12:06 am PST Tonight --/ 49° 5% Sat 23 | Night 49° 5% NNW 2 mph Partly cloudy this evening, then becoming foggy and damp after midnight. Low 49F. Winds...\"}, {\"url\": \"https://weather.com/weather/hourbyhour/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2\", \"content\": \"recents Specialty Forecasts Hourly Weather-Los Angeles, CA Air Quality Alert Tuesday, December 26 10 am Partly Cloudy Cold & Flu Forecast Flu risk is low in your area 3 pm Partly Cloudy 4 pm Mostly Cloudy 5 pm Mostly Cloudy 6 pm Mostly Cloudy 7 pm Cloudy 8 pm Cloudy 9 pm Mostly Cloudy 10 am Cloudy 11 am Mostly Cloudy 12 pm Cloudy 1 pm Mostly Cloudy 2 pm Cloudy 3 pm Cloudy 4 pm Cloudy 5 pm Cloudy 6 pmHourly Weather Forecast for Los Angeles, CA - The Weather Channel | Weather.com - Los Angeles, CA As of 11:12 am PST Saturday, December 23 12 pm 64° 4% Mostly Cloudy Feels Like 64°...\"}]', name='tavily_search_results_json')]}\n", + "{'actions': [...], 'messages': [...]}\n", "------\n", - "{'output': \"The weather in San Francisco is currently foggy early, then partly cloudy later in the day with a high around 60°F. There is a chance of rain showers with a high of 59°F tomorrow. The temperature will drop to a low of 46°F on Thursday night with cloudy conditions and showers.\\n\\nIn Los Angeles, there is considerable cloudiness with occasional rain showers today. The high temperature is expected to be 64°F with light and variable winds. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 62°F. The weather will be overcast with showers at times on Wednesday with a high of 66°F.\\n\\nPlease note that weather conditions can change rapidly, so it's always a good idea to check a reliable weather source for the most up-to-date information.\", 'messages': [AIMessage(content=\"The weather in San Francisco is currently foggy early, then partly cloudy later in the day with a high around 60°F. There is a chance of rain showers with a high of 59°F tomorrow. The temperature will drop to a low of 46°F on Thursday night with cloudy conditions and showers.\\n\\nIn Los Angeles, there is considerable cloudiness with occasional rain showers today. The high temperature is expected to be 64°F with light and variable winds. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 62°F. The weather will be overcast with showers at times on Wednesday with a high of 66°F.\\n\\nPlease note that weather conditions can change rapidly, so it's always a good idea to check a reliable weather source for the most up-to-date information.\")]}\n", - "------\n" + "{'messages': [...], 'steps': [...]}\n", + "------\n", + "{'messages': [...],\n", + " 'output': 'The items located where the cat is hiding on the shelf are books, '\n", + " 'pencils, and pictures.'}\n" ] } ], "source": [ - "for chunk in agent_executor.stream({\"input\": \"what is the weather in SF and then LA\"}):\n", - " print(chunk)\n", - " print(\"------\")" + "# Note: We use `pprint` to print only to depth 1, it makes it easier to see the output from a high level, before digging in.\n", + "import pprint\n", + "\n", + "chunks = []\n", + "\n", + "async for chunk in agent_executor.astream(\n", + " {\"input\": \"what's items are located where the cat is hiding?\"}\n", + "):\n", + " chunks.append(chunk)\n", + " print(\"------\")\n", + " pprint.pprint(chunk, depth=1)" ] }, { "cell_type": "markdown", - "id": "433c78f0", + "id": "76a930c7-7c6f-4602-b265-d38018f067be", "metadata": {}, "source": [ - "You can see that we get back a bunch of different information. There are two ways to work with this information:\n", + "### Using Messages\n", "\n", - "1. By using the AgentAction/observation/AgentFinish object\n", - "2. By using the `messages` object\n", - "\n", - "You may prefer to use the `messages` object if you are working with a chatbot - because these are chat messages and can be rendered directly. If you don't care about that, the AgentAction/observation/AgentFinish is probably the easier one to inspect." + "You can access the underlying `messages` from the outputs. Using messages can be nice when working with chat applications - because everything is a message!" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4d5a3112-b2d4-488a-ac76-aa40dcec9cfe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pKy4OLcBx6pR6k3GHBOlH68r', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_pKy4OLcBx6pR6k3GHBOlH68r')]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chunks[0][\"actions\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2f5eead3-f6f0-40b7-82c7-3b485c634e94", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pKy4OLcBx6pR6k3GHBOlH68r', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})]\n", + "[FunctionMessage(content='on the shelf', name='where_cat_is_hiding')]\n", + "[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_qZTz1mRfCCXT18SUy0E07eS4', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]})]\n", + "[FunctionMessage(content='books, penciles and pictures', name='get_items')]\n", + "[AIMessage(content='The items located where the cat is hiding on the shelf are books, pencils, and pictures.')]\n" + ] + } + ], + "source": [ + "for chunk in chunks:\n", + " print(chunk[\"messages\"])" + ] + }, + { + "cell_type": "markdown", + "id": "1397f859-8595-488e-9857-c4e090a136d3", + "metadata": {}, + "source": [ + "In addition, they contain full logging information (`actions` and `steps`) which may be easier to process for rendering purposes." ] }, { @@ -135,14 +314,16 @@ "id": "edd291a7", "metadata": {}, "source": [ - "### Using AgentAction/observation/AgentFinish\n", + "### Using AgentAction/Observation\n", "\n", - "You can access these raw objects as part of the streamed payload. This gives you more low level information, but can be harder to parse." + "The outputs also contain richer structured information inside of `actions` and `steps`, which could be useful in some situations, but can also be harder to parse.\n", + "\n", + "**Attention** `AgentFinish` is not available as part of the `streaming` method. If this is something you'd like to be added, please start a discussion on github and explain why its needed." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "id": "603bff1d", "metadata": {}, "outputs": [ @@ -150,109 +331,287 @@ "name": "stdout", "output_type": "stream", "text": [ - "Calling Tool ```tavily_search_results_json``` with input ```{'query': 'weather in San Francisco'}```\n", - "------\n", - "Got result: ```[{'url': 'https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US', 'content': 'recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...'}]```\n", - "------\n", - "Calling Tool ```tavily_search_results_json``` with input ```{'query': 'weather in Los Angeles'}```\n", - "------\n", - "Got result: ```[{'url': 'https://hoodline.com/2023/12/los-angeles-hit-with-no-burn-order-during-cloudy-holiday-forecast-aqmd-urges-compliance-for-public-health/', 'content': 'skies and a chance of rain. According to the National Weather Service, today’s weather in Los Angeles is mostly sunny visiting the AQMD site or using its mobile app. While Los Angeles navigates through a cloudy and cooler weather Weather & Environment in ... Los Angeles Hit with No-Burn Order During Cloudy Holiday Forecast and cooler weather pattern, with temperatures fluctuating around the high 60 degrees and chances of rain by FridayPublished on December 26, 2023. Los Angeles residents face a restricted holiday season as the South Coast Air Quality Management District (AQMD) extends a mandatory no-burn order amid multiple ...'}, {'url': 'https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2', 'content': 'recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Tonight Sat 23 | Night Considerable cloudiness with occasional rain showers. Low 48F. Winds light and variable. Chance of rain 60%. Thu 04 Mon 01 | Day Considerable clouds early. Some decrease in clouds later in the day. High 66F. Winds light and variable. Thu 04 | Night Showers early becoming less numerous late. Low 48F. Winds light and variable. Chance of rain 40%. Fri 05Today 66°/ 50° 6% Sun 24 | Day 66° 6% WSW 4 mph Partly cloudy skies. High 66F. Winds light and variable. Humidity 65% UV Index 3 of 11 Sunrise 6:56 am Sunset 4:49 pm Sun 24 | Night 50° 10% N 1 mph...'}, {'url': 'https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=d4a04df2f5cd7d6ef329c49238253e994619763fd5f77a424ca3f1af9957e717', 'content': 'recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Tonight Sat 23 | Night Considerable cloudiness with occasional rain showers. Low 48F. Winds light and variable. Chance of rain 60%. Thu 04 Mon 01 | Day Considerable clouds early. Some decrease in clouds later in the day. High 66F. Winds light and variable. Thu 04 | Night Showers early becoming less numerous late. Low 48F. Winds light and variable. Chance of rain 40%. Fri 0510 Day Weather - Los Angeles, CA As of 12:06 am PST Tonight --/ 49° 5% Sat 23 | Night 49° 5% NNW 2 mph Partly cloudy this evening, then becoming foggy and damp after midnight. Low 49F. Winds...'}]```\n", - "------\n", - "The weather in San Francisco is currently foggy early, then partly cloudy later in the day with a high around 60°F. There is a chance of rain showers with a high of 59°F tomorrow. The temperature will drop to a low of 46°F on Thursday night with cloudy skies and showers.\n", - "\n", - "In Los Angeles, there is considerable cloudiness with occasional rain showers. The temperature will drop to a low of 48°F tonight with light and variable winds. Tomorrow, there will be considerable clouds early with some decrease in clouds later in the day and a high of 66°F. Showers are expected in the evening with a low of 48°F.\n", - "------\n" + "Calling Tool: `where_cat_is_hiding` with input `{}`\n", + "---\n", + "Tool Result: `on the shelf`\n", + "---\n", + "Calling Tool: `get_items` with input `{'place': 'shelf'}`\n", + "---\n", + "Tool Result: `books, penciles and pictures`\n", + "---\n", + "Final Output: The items located where the cat is hiding on the shelf are books, pencils, and pictures.\n", + "---\n" ] } ], "source": [ - "for chunk in agent_executor.stream({\"input\": \"what is the weather in SF and then LA\"}):\n", + "async for chunk in agent_executor.astream(\n", + " {\"input\": \"what's items are located where the cat is hiding?\"}\n", + "):\n", " # Agent Action\n", " if \"actions\" in chunk:\n", " for action in chunk[\"actions\"]:\n", - " print(\n", - " f\"Calling Tool ```{action.tool}``` with input ```{action.tool_input}```\"\n", - " )\n", + " print(f\"Calling Tool: `{action.tool}` with input `{action.tool_input}`\")\n", " # Observation\n", " elif \"steps\" in chunk:\n", " for step in chunk[\"steps\"]:\n", - " print(f\"Got result: ```{step.observation}```\")\n", + " print(f\"Tool Result: `{step.observation}`\")\n", " # Final result\n", " elif \"output\" in chunk:\n", - " print(chunk[\"output\"])\n", + " print(f'Final Output: {chunk[\"output\"]}')\n", " else:\n", - " raise ValueError\n", - " print(\"------\")" + " raise ValueError()\n", + " print(\"---\")" ] }, { + "attachments": {}, "cell_type": "markdown", - "id": "72df7b43", + "id": "5058f098-d8b5-4500-bd99-b972af3ecc09", "metadata": {}, "source": [ - "### Using messages\n", + "## Custom Streaming With Events\n", "\n", - "Using messages can be nice when working with chat applications - because everything is a message!" + "Use the `astream_events` API in case the default behavior of *stream* does not work for your application (e.g., if you need to stream individual tokens from the agent or surface steps occuring **within** tools).\n", + "\n", + "⚠️ This is a **beta** API, meaning that some details might change slightly in the future based on usage.\n", + "⚠️ To make sure all callbacks work properly, use `async` code throughout. Try avoiding mixing in sync versions of code (e.g., sync versions of tools).\n", + "\n", + "Let's use this API to stream the following events:\n", + "\n", + "1. Agent Start with inputs\n", + "1. Tool Start with inputs\n", + "1. Tool End with outputs\n", + "1. Stream the agent final anwer token by token\n", + "1. Agent End with outputs" ] }, { "cell_type": "code", - "execution_count": 7, - "id": "ca79c8d9", + "execution_count": 11, + "id": "46c59cac-25fa-4f42-8cf2-9bcaed6d92c4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]\n", - "------\n", - "[FunctionMessage(content='[{\"url\": \"https://www.cbsnews.com/sanfrancisco/news/next-round-of-rain-set-to-arrive-in-bay-area-wednesday-morning/\", \"content\": \"weather persists through Thursday morning. The second system is projected to get to the Bay Area early Friday, Watch CBS News Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST to the Bay Area on Wednesday with the arrival of the first of two storm systems. Overnight lows should be mostly in the 40s in the region, with some areas around the bay dropping into the 50s.Watch CBS News Weather Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST / CBS/Bay City News Service While the outlook on Tuesday is cloudy and...\"}, {\"url\": \"https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US\", \"content\": \"recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...\"}]', name='tavily_search_results_json')]\n", - "------\n", - "[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in Los Angeles\"\\n}', 'name': 'tavily_search_results_json'}})]\n", - "------\n", - "[FunctionMessage(content='[{\"url\": \"https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=d4a04df2f5cd7d6ef329c49238253e994619763fd5f77a424ca3f1af9957e717\", \"content\": \"recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Tonight Sat 23 | Night Considerable cloudiness with occasional rain showers. Low 48F. Winds light and variable. Chance of rain 60%. Thu 04 Mon 01 | Day Considerable clouds early. Some decrease in clouds later in the day. High 66F. Winds light and variable. Thu 04 | Night Showers early becoming less numerous late. Low 48F. Winds light and variable. Chance of rain 40%. Fri 0510 Day Weather - Los Angeles, CA As of 12:06 am PST Tonight --/ 49° 5% Sat 23 | Night 49° 5% NNW 2 mph Partly cloudy this evening, then becoming foggy and damp after midnight. Low 49F. Winds...\"}, {\"url\": \"https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2\", \"content\": \"recents Specialty Forecasts 10 Day Weather-Los Angeles, CA Tonight Sat 23 | Night Considerable cloudiness with occasional rain showers. Low 48F. Winds light and variable. Chance of rain 60%. Thu 04 Mon 01 | Day Considerable clouds early. Some decrease in clouds later in the day. High 66F. Winds light and variable. Thu 04 | Night Showers early becoming less numerous late. Low 48F. Winds light and variable. Chance of rain 40%. Fri 05Today 66°/ 50° 6% Sun 24 | Day 66° 6% WSW 4 mph Partly cloudy skies. High 66F. Winds light and variable. Humidity 65% UV Index 3 of 11 Sunrise 6:56 am Sunset 4:49 pm Sun 24 | Night 50° 10% N 1 mph...\"}, {\"url\": \"https://abc7.com/weather/\", \"content\": \"WATCH LIVE AccuWeather in the region are forecast to be high.More in the region are forecast to be high.More NOW IN EFFECT FROM 4 AM THURSDAY TO 10 PM PST SATURDAY...MoreToday\\'s Weather Los Angeles, CA Current Today Tonight MOSTLY CLOUDY 65 ° Feels Like 65° Sunrise 6:55 AM Humidity 65% Sunset 4:48 PM Windspeed ESE 3 mph Moonrise 2:08 PM Pressure 30.0 in...\"}]', name='tavily_search_results_json')]\n", - "------\n", - "[AIMessage(content='The weather in San Francisco is expected to have rain on Wednesday and Thursday. The temperature will range from the 40s to the 50s. You can find more information [here](https://www.cbsnews.com/sanfrancisco/news/next-round-of-rain-set-to-arrive-in-bay-area-wednesday-morning/) and [here](https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US).\\n\\nThe weather in Los Angeles is expected to have occasional rain showers with temperatures ranging from the 40s to the 60s. You can find more information [here](https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=d4a04df2f5cd7d6ef329c49238253e994619763fd5f77a424ca3f1af9957e717) and [here](https://weather.com/weather/tenday/l/Los+Angeles+CA?canonicalCityId=84c64154109916077c8d3c2352410aaae5f6eeff682000e3a7470e38976128c2).')]\n", - "------\n" + "Starting agent: Agent with input: {'input': 'where is the cat hiding? what items are in that location?'}\n", + "--\n", + "Starting tool: where_cat_is_hiding with inputs: {}\n", + "Done tool: where_cat_is_hiding\n", + "Tool output was: on the shelf\n", + "--\n", + "--\n", + "Starting tool: get_items with inputs: {'place': 'shelf'}\n", + "Done tool: get_items\n", + "Tool output was: books, penciles and pictures\n", + "--\n", + "The| cat| is| currently| hiding| on| the| shelf|.| In| that| location|,| you| can| find| books|,| pencils|,| and| pictures|.|\n", + "--\n", + "Done agent: Agent with output: The cat is currently hiding on the shelf. In that location, you can find books, pencils, and pictures.\n" ] } ], "source": [ - "for chunk in agent_executor.stream({\"input\": \"what is the weather in SF and then LA\"}):\n", - " print(chunk[\"messages\"])\n", - " print(\"------\")" + "async for event in agent_executor.astream_events(\n", + " {\"input\": \"where is the cat hiding? what items are in that location?\"},\n", + " version=\"v1\",\n", + "):\n", + " kind = event[\"event\"]\n", + " if kind == \"on_chain_start\":\n", + " if (\n", + " event[\"name\"] == \"Agent\"\n", + " ): # Was assigned when creating the agent with `.with_config({\"run_name\": \"Agent\"})`\n", + " print(\n", + " f\"Starting agent: {event['name']} with input: {event['data'].get('input')}\"\n", + " )\n", + " elif kind == \"on_chain_end\":\n", + " if (\n", + " event[\"name\"] == \"Agent\"\n", + " ): # Was assigned when creating the agent with `.with_config({\"run_name\": \"Agent\"})`\n", + " print()\n", + " print(\"--\")\n", + " print(\n", + " f\"Done agent: {event['name']} with output: {event['data'].get('output')['output']}\"\n", + " )\n", + " if kind == \"on_chat_model_stream\":\n", + " content = event[\"data\"][\"chunk\"].content\n", + " if content:\n", + " # Empty content in the context of OpenAI means\n", + " # that the model is asking for a tool to be invoked.\n", + " # So we only print non-empty content\n", + " print(content, end=\"|\")\n", + " elif kind == \"on_tool_start\":\n", + " print(\"--\")\n", + " print(\n", + " f\"Starting tool: {event['name']} with inputs: {event['data'].get('input')}\"\n", + " )\n", + " elif kind == \"on_tool_end\":\n", + " print(f\"Done tool: {event['name']}\")\n", + " print(f\"Tool output was: {event['data'].get('output')}\")\n", + " print(\"--\")" ] }, { "cell_type": "markdown", - "id": "0dc01b0f", + "id": "09711ba8-f60e-4a5d-9ace-1bdc613a7c44", "metadata": {}, "source": [ - "## Stream tokens\n", + "### Stream Events from within Tools\n", "\n", - "In addition to streaming the final result, you can also stream tokens. This will require slightly more complicated parsing of the logs\n", + "If your tool leverages LangChain runnable objects (e.g., LCEL chains, LLMs, retrievers etc.) and you want to stream events from those objects as well, you'll need to make sure that callbacks are propagated correctly.\n", "\n", - "You will also need to make sure you set the LLM to be streaming" + "To see how to pass callbacks, let's re-implement the `get_items` tool to make it use an LLM and pass callbacks to that LLM. Feel free to adapt this to your use case." ] }, { "cell_type": "code", - "execution_count": 8, - "id": "3e92d09d", + "execution_count": 12, + "id": "fdd005f4-31d3-450f-b16b-b614c26a72f3", "metadata": {}, "outputs": [], "source": [ - "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0, streaming=True)\n", + "@tool\n", + "async def get_items(place: str, callbacks: Callbacks) -> str: # <--- Accept callbacks\n", + " \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n", + " template = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"human\",\n", + " \"Can you tell me what kind of items i might find in the following place: '{place}'. \"\n", + " \"List at least 3 such items separating them by a comma. And include a brief description of each item..\",\n", + " )\n", + " ]\n", + " )\n", + " chain = template | model.with_config(\n", + " {\n", + " \"run_name\": \"Get Items LLM\",\n", + " \"tags\": [\"tool_llm\"],\n", + " \"callbacks\": callbacks, # <-- Propagate callbacks\n", + " }\n", + " )\n", + " chunks = [chunk async for chunk in chain.astream({\"place\": place})]\n", + " return \"\".join(chunk.content for chunk in chunks)" + ] + }, + { + "cell_type": "markdown", + "id": "66828308-538f-4a06-8ed6-bf398d7a3d56", + "metadata": {}, + "source": [ + "^ Take a look at how the tool propagates callbacks. \n", "\n", - "agent = create_openai_functions_agent(llm, tools, prompt)\n", - "agent_executor = AgentExecutor(agent=agent, tools=tools)" + "Next, let's initialize our agent, and take a look at the new output." ] }, { "cell_type": "code", - "execution_count": 9, - "id": "753ff598", + "execution_count": 13, + "id": "095df835-ab27-4791-80e9-07cdba180822", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting agent: Agent with input: {'input': 'where is the cat hiding? what items are in that location?'}\n", + "--\n", + "Starting tool: where_cat_is_hiding with inputs: {}\n", + "Done tool: where_cat_is_hiding\n", + "Tool output was: on the shelf\n", + "--\n", + "--\n", + "Starting tool: get_items with inputs: {'place': 'shelf'}\n", + "In| a| shelf|,| you| might| find|:\n", + "\n", + "|1|.| Books|:| A| shelf| is| commonly| used| to| store| books|.| It| may| contain| various| genres| such| as| novels|,| textbooks|,| or| reference| books|.| Books| provide| knowledge|,| entertainment|,| and| can| transport| you| to| different| worlds| through| storytelling|.\n", + "\n", + "|2|.| Decor|ative| items|:| Sh|elves| often| display| decorative| items| like| figur|ines|,| v|ases|,| or| photo| frames|.| These| items| add| a| personal| touch| to| the| space| and| can| reflect| the| owner|'s| interests| or| memories|.\n", + "\n", + "|3|.| Storage| boxes|:| Sh|elves| can| also| hold| storage| boxes| or| baskets|.| These| containers| help| organize| and| decl|utter| the| space| by| storing| miscellaneous| items| like| documents|,| accessories|,| or| small| household| items|.| They| provide| a| neat| and| tidy| appearance| to| the| shelf|.|Done tool: get_items\n", + "Tool output was: In a shelf, you might find:\n", + "\n", + "1. Books: A shelf is commonly used to store books. It may contain various genres such as novels, textbooks, or reference books. Books provide knowledge, entertainment, and can transport you to different worlds through storytelling.\n", + "\n", + "2. Decorative items: Shelves often display decorative items like figurines, vases, or photo frames. These items add a personal touch to the space and can reflect the owner's interests or memories.\n", + "\n", + "3. Storage boxes: Shelves can also hold storage boxes or baskets. These containers help organize and declutter the space by storing miscellaneous items like documents, accessories, or small household items. They provide a neat and tidy appearance to the shelf.\n", + "--\n", + "The| cat| is| hiding| on| the| shelf|.| In| that| location|,| you| might| find| books|,| decorative| items|,| and| storage| boxes|.|\n", + "--\n", + "Done agent: Agent with output: The cat is hiding on the shelf. In that location, you might find books, decorative items, and storage boxes.\n" + ] + } + ], + "source": [ + "# Get the prompt to use - you can modify this!\n", + "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", + "# print(prompt.messages) -- to see the prompt\n", + "tools = [get_items, where_cat_is_hiding]\n", + "agent = create_openai_tools_agent(\n", + " model.with_config({\"tags\": [\"agent_llm\"]}), tools, prompt\n", + ")\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools).with_config(\n", + " {\"run_name\": \"Agent\"}\n", + ")\n", + "\n", + "async for event in agent_executor.astream_events(\n", + " {\"input\": \"where is the cat hiding? what items are in that location?\"},\n", + " version=\"v1\",\n", + "):\n", + " kind = event[\"event\"]\n", + " if kind == \"on_chain_start\":\n", + " if (\n", + " event[\"name\"] == \"Agent\"\n", + " ): # Was assigned when creating the agent with `.with_config({\"run_name\": \"Agent\"})`\n", + " print(\n", + " f\"Starting agent: {event['name']} with input: {event['data'].get('input')}\"\n", + " )\n", + " elif kind == \"on_chain_end\":\n", + " if (\n", + " event[\"name\"] == \"Agent\"\n", + " ): # Was assigned when creating the agent with `.with_config({\"run_name\": \"Agent\"})`\n", + " print()\n", + " print(\"--\")\n", + " print(\n", + " f\"Done agent: {event['name']} with output: {event['data'].get('output')['output']}\"\n", + " )\n", + " if kind == \"on_chat_model_stream\":\n", + " content = event[\"data\"][\"chunk\"].content\n", + " if content:\n", + " # Empty content in the context of OpenAI means\n", + " # that the model is asking for a tool to be invoked.\n", + " # So we only print non-empty content\n", + " print(content, end=\"|\")\n", + " elif kind == \"on_tool_start\":\n", + " print(\"--\")\n", + " print(\n", + " f\"Starting tool: {event['name']} with inputs: {event['data'].get('input')}\"\n", + " )\n", + " elif kind == \"on_tool_end\":\n", + " print(f\"Done tool: {event['name']}\")\n", + " print(f\"Tool output was: {event['data'].get('output')}\")\n", + " print(\"--\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "24386754-5cd6-4322-82f7-affb93322bad", + "metadata": {}, + "source": [ + "### Other aproaches\n", + "\n", + "#### Using astream_log\n", + "\n", + "**Note** You can also use the [astream_log](https://python.langchain.com/docs/expression_language/interface#async-stream-intermediate-steps) API. This API produces a granular log of all events that occur during execution. The log format is based on the [JSONPatch](https://jsonpatch.com/) standard. It's granular, but requires effort to parse. For this reason, we created the `astream_events` API instead." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "01ad657f-7759-4fb3-a7ca-e2d7e7f8b28f", "metadata": {}, "outputs": [ { @@ -262,522 +621,100 @@ "RunLogPatch({'op': 'replace',\n", " 'path': '',\n", " 'value': {'final_output': None,\n", - " 'id': '32650ba8-8a53-4b76-8846-dbb6c3a65727',\n", + " 'id': 'c261bc30-60d1-4420-9c66-c6c0797f2c2d',\n", " 'logs': {},\n", - " 'streamed_output': []}})\n", + " 'name': 'Agent',\n", + " 'streamed_output': [],\n", + " 'type': 'chain'}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI',\n", + " 'path': '/logs/RunnableSequence',\n", " 'value': {'end_time': None,\n", " 'final_output': None,\n", - " 'id': 'ce3a507d-210d-40aa-8576-dd0aa97e6498',\n", + " 'id': '183cb6f8-ed29-4967-b1ea-024050ce66c7',\n", " 'metadata': {},\n", - " 'name': 'ChatOpenAI',\n", - " 'start_time': '2023-12-26T17:55:56.653',\n", + " 'name': 'RunnableSequence',\n", + " 'start_time': '2024-01-22T20:38:43.650+00:00',\n", " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['seq:step:3'],\n", - " 'type': 'llm'}})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': '{\\n', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': ' ', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': ' \"', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': '\":', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': ' \"', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': 'weather', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': ' in', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': ' San', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': ' Francisco', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': '\"\\n', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='', additional_kwargs={'function_call': {'arguments': '}', 'name': ''}})})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/streamed_output/-',\n", - " 'value': AIMessageChunk(content='')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/final_output',\n", - " 'value': {'generations': [[{'generation_info': {'finish_reason': 'function_call'},\n", - " 'message': AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}),\n", - " 'text': '',\n", - " 'type': 'ChatGeneration'}]],\n", - " 'llm_output': None,\n", - " 'run': None}},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI/end_time',\n", - " 'value': '2023-12-26T17:55:57.337'})\n", + " 'tags': [],\n", + " 'type': 'chain'}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/streamed_output/-',\n", - " 'value': {'actions': [AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})])],\n", - " 'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}},\n", - " {'op': 'replace',\n", - " 'path': '/final_output',\n", - " 'value': {'actions': [AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})])],\n", - " 'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/streamed_output/-',\n", - " 'value': {'messages': [FunctionMessage(content='[{\"url\": \"https://www.cbsnews.com/sanfrancisco/news/next-round-of-rain-set-to-arrive-in-bay-area-wednesday-morning/\", \"content\": \"weather persists through Thursday morning. The second system is projected to get to the Bay Area early Friday, Watch CBS News Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST to the Bay Area on Wednesday with the arrival of the first of two storm systems. Overnight lows should be mostly in the 40s in the region, with some areas around the bay dropping into the 50s.Watch CBS News Weather Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST / CBS/Bay City News Service While the outlook on Tuesday is cloudy and...\"}, {\"url\": \"https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US\", \"content\": \"recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...\"}]', name='tavily_search_results_json')],\n", - " 'steps': [AgentStep(action=AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]), observation=[{'url': 'https://www.cbsnews.com/sanfrancisco/news/next-round-of-rain-set-to-arrive-in-bay-area-wednesday-morning/', 'content': 'weather persists through Thursday morning. The second system is projected to get to the Bay Area early Friday, Watch CBS News Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST to the Bay Area on Wednesday with the arrival of the first of two storm systems. Overnight lows should be mostly in the 40s in the region, with some areas around the bay dropping into the 50s.Watch CBS News Weather Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST / CBS/Bay City News Service While the outlook on Tuesday is cloudy and...'}, {'url': 'https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US', 'content': 'recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...'}])]}},\n", - " {'op': 'add',\n", - " 'path': '/final_output/steps',\n", - " 'value': [AgentStep(action=AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]), observation=[{'url': 'https://www.cbsnews.com/sanfrancisco/news/next-round-of-rain-set-to-arrive-in-bay-area-wednesday-morning/', 'content': 'weather persists through Thursday morning. The second system is projected to get to the Bay Area early Friday, Watch CBS News Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST to the Bay Area on Wednesday with the arrival of the first of two storm systems. Overnight lows should be mostly in the 40s in the region, with some areas around the bay dropping into the 50s.Watch CBS News Weather Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST / CBS/Bay City News Service While the outlook on Tuesday is cloudy and...'}, {'url': 'https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US', 'content': 'recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...'}])]},\n", - " {'op': 'add',\n", - " 'path': '/final_output/messages/1',\n", - " 'value': FunctionMessage(content='[{\"url\": \"https://www.cbsnews.com/sanfrancisco/news/next-round-of-rain-set-to-arrive-in-bay-area-wednesday-morning/\", \"content\": \"weather persists through Thursday morning. The second system is projected to get to the Bay Area early Friday, Watch CBS News Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST to the Bay Area on Wednesday with the arrival of the first of two storm systems. Overnight lows should be mostly in the 40s in the region, with some areas around the bay dropping into the 50s.Watch CBS News Weather Next round of rain set to arrive in Bay Area Wednesday morning December 26, 2023 / 8:17 AM PST / CBS/Bay City News Service While the outlook on Tuesday is cloudy and...\"}, {\"url\": \"https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US\", \"content\": \"recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...\"}]', name='tavily_search_results_json')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2',\n", + " 'path': '/logs/RunnableAssign<agent_scratchpad>',\n", " 'value': {'end_time': None,\n", " 'final_output': None,\n", - " 'id': '503b959e-b6c2-427b-b2e8-7d40497a2458',\n", + " 'id': '7fe1bb27-3daf-492e-bc7e-28602398f008',\n", " 'metadata': {},\n", - " 'name': 'ChatOpenAI',\n", - " 'start_time': '2023-12-26T17:56:00.983',\n", + " 'name': 'RunnableAssign<agent_scratchpad>',\n", + " 'start_time': '2024-01-22T20:38:43.652+00:00',\n", " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['seq:step:3'],\n", - " 'type': 'llm'}})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI:2/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': 'The'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='The')})\n", + " 'tags': ['seq:step:1'],\n", + " 'type': 'chain'}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' weather'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' weather')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' in'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' in')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' San'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' San')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' Francisco'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' Francisco')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' is'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' is')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' currently'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' currently')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' cloudy'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' cloudy')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' with'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' with')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' occasional'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' occasional')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' rain'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' rain')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' showers'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' showers')})\n", + " 'path': '/logs/RunnableAssign<agent_scratchpad>/streamed_output/-',\n", + " 'value': {'input': 'where is the cat hiding? what items are in that '\n", + " 'location?',\n", + " 'intermediate_steps': []}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '.'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='.')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' The'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' The')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' temperature'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' temperature')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' is'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' is')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' around'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' around')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' '},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' ')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '59'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='59')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '°F'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='°F')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' ('},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' (')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '15'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='15')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '°C'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='°C')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ')'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=')')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' with'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' with')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' winds'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' winds')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' from'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' from')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' the'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' the')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' southeast'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' southeast')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' at'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' at')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' '},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' ')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '5'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='5')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' to'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' to')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' '},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' ')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '10'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='10')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' mph'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' mph')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '.'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='.')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' The'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' The')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' overnight'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' overnight')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' low'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' low')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' is'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' is')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' expected'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' expected')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' to'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' to')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' be'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' be')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' around'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' around')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' '},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' ')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '46'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='46')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '°F'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='°F')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' ('},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' (')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '8'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='8')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '°C'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='°C')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ')'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=')')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' with'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' with')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' a'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' a')})\n", - "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' chance'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' chance')})\n", + " 'path': '/logs/RunnableParallel<agent_scratchpad>',\n", + " 'value': {'end_time': None,\n", + " 'final_output': None,\n", + " 'id': 'b034e867-e6bb-4296-bfe6-752c44fba6ce',\n", + " 'metadata': {},\n", + " 'name': 'RunnableParallel<agent_scratchpad>',\n", + " 'start_time': '2024-01-22T20:38:43.652+00:00',\n", + " 'streamed_output': [],\n", + " 'streamed_output_str': [],\n", + " 'tags': [],\n", + " 'type': 'chain'}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' of'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' of')})\n", + " 'path': '/logs/RunnableLambda',\n", + " 'value': {'end_time': None,\n", + " 'final_output': None,\n", + " 'id': '65ceef3e-7a80-4015-8b5b-d949326872e9',\n", + " 'metadata': {},\n", + " 'name': 'RunnableLambda',\n", + " 'start_time': '2024-01-22T20:38:43.653+00:00',\n", + " 'streamed_output': [],\n", + " 'streamed_output_str': [],\n", + " 'tags': ['map:key:agent_scratchpad'],\n", + " 'type': 'chain'}})\n", + "RunLogPatch({'op': 'add', 'path': '/logs/RunnableLambda/streamed_output/-', 'value': []})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': ' showers'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content=' showers')})\n", + " 'path': '/logs/RunnableParallel<agent_scratchpad>/streamed_output/-',\n", + " 'value': {'agent_scratchpad': []}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output_str/-',\n", - " 'value': '.'},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='.')})\n", - "RunLogPatch({'op': 'add', 'path': '/logs/ChatOpenAI:2/streamed_output_str/-', 'value': ''},\n", - " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/streamed_output/-',\n", - " 'value': AIMessageChunk(content='')})\n", + " 'path': '/logs/RunnableAssign<agent_scratchpad>/streamed_output/-',\n", + " 'value': {'agent_scratchpad': []}})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/final_output',\n", - " 'value': {'generations': [[{'generation_info': {'finish_reason': 'stop'},\n", - " 'message': AIMessage(content='The weather in San Francisco is currently cloudy with occasional rain showers. The temperature is around 59°F (15°C) with winds from the southeast at 5 to 10 mph. The overnight low is expected to be around 46°F (8°C) with a chance of showers.'),\n", - " 'text': 'The weather in San Francisco is '\n", - " 'currently cloudy with occasional rain '\n", - " 'showers. The temperature is around 59°F '\n", - " '(15°C) with winds from the southeast at '\n", - " '5 to 10 mph. The overnight low is '\n", - " 'expected to be around 46°F (8°C) with a '\n", - " 'chance of showers.',\n", - " 'type': 'ChatGeneration'}]],\n", - " 'llm_output': None,\n", - " 'run': None}},\n", + " 'path': '/logs/RunnableLambda/final_output',\n", + " 'value': {'output': []}},\n", " {'op': 'add',\n", - " 'path': '/logs/ChatOpenAI:2/end_time',\n", - " 'value': '2023-12-26T17:56:02.356'})\n", + " 'path': '/logs/RunnableLambda/end_time',\n", + " 'value': '2024-01-22T20:38:43.654+00:00'})\n", "RunLogPatch({'op': 'add',\n", - " 'path': '/streamed_output/-',\n", - " 'value': {'messages': [AIMessage(content='The weather in San Francisco is currently cloudy with occasional rain showers. The temperature is around 59°F (15°C) with winds from the southeast at 5 to 10 mph. The overnight low is expected to be around 46°F (8°C) with a chance of showers.')],\n", - " 'output': 'The weather in San Francisco is currently cloudy with '\n", - " 'occasional rain showers. The temperature is around 59°F '\n", - " '(15°C) with winds from the southeast at 5 to 10 mph. '\n", - " 'The overnight low is expected to be around 46°F (8°C) '\n", - " 'with a chance of showers.'}},\n", + " 'path': '/logs/RunnableParallel<agent_scratchpad>/final_output',\n", + " 'value': {'agent_scratchpad': []}},\n", " {'op': 'add',\n", - " 'path': '/final_output/output',\n", - " 'value': 'The weather in San Francisco is currently cloudy with occasional '\n", - " 'rain showers. The temperature is around 59°F (15°C) with winds '\n", - " 'from the southeast at 5 to 10 mph. The overnight low is expected '\n", - " 'to be around 46°F (8°C) with a chance of showers.'},\n", - " {'op': 'add',\n", - " 'path': '/final_output/messages/2',\n", - " 'value': AIMessage(content='The weather in San Francisco is currently cloudy with occasional rain showers. The temperature is around 59°F (15°C) with winds from the southeast at 5 to 10 mph. The overnight low is expected to be around 46°F (8°C) with a chance of showers.')})\n" + " 'path': '/logs/RunnableParallel<agent_scratchpad>/end_time',\n", + " 'value': '2024-01-22T20:38:43.655+00:00'})\n" ] } ], "source": [ + "i = 0\n", "async for chunk in agent_executor.astream_log(\n", - " {\"input\": \"what is the weather in sf\", \"chat_history\": []},\n", - " include_names=[\"ChatOpenAI\"],\n", + " {\"input\": \"where is the cat hiding? what items are in that location?\"},\n", "):\n", - " print(chunk)" + " print(chunk)\n", + " i += 1\n", + " if i > 10:\n", + " break" ] }, { "cell_type": "markdown", - "id": "51a51076", + "id": "5763c64b-7fff-4167-9eb3-172209cef958", "metadata": {}, "source": [ "This may require some logic to get in a workable format" @@ -785,8 +722,8 @@ }, { "cell_type": "code", - "execution_count": 10, - "id": "7cdae318", + "execution_count": 15, + "id": "f7120cbd-6bea-4706-821a-ff3b6722bf1d", "metadata": {}, "outputs": [ { @@ -796,272 +733,104 @@ "\n", "None\n", "----\n", - "/logs/ChatOpenAI\n", - "{'id': '3f6d3587-600f-419b-8225-8908a347b7d2', 'name': 'ChatOpenAI', 'type': 'llm', 'tags': ['seq:step:3'], 'metadata': {}, 'start_time': '2023-12-26T17:56:19.884', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", + "/logs/RunnableSequence\n", + "{'id': '22bbd5db-9578-4e3f-a6ec-9b61f08cb8a9', 'name': 'RunnableSequence', 'type': 'chain', 'tags': [], 'metadata': {}, 'start_time': '2024-01-22T20:38:43.668+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableAssign<agent_scratchpad>\n", + "{'id': 'e0c00ae2-aaa2-4a09-bc93-cb34bf3f6554', 'name': 'RunnableAssign<agent_scratchpad>', 'type': 'chain', 'tags': ['seq:step:1'], 'metadata': {}, 'start_time': '2024-01-22T20:38:43.672+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableAssign<agent_scratchpad>/streamed_output/-\n", + "{'input': 'where is the cat hiding? what items are in that location?', 'intermediate_steps': []}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n ', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableParallel<agent_scratchpad>\n", + "{'id': '26ff576d-ff9d-4dea-98b2-943312a37f4d', 'name': 'RunnableParallel<agent_scratchpad>', 'type': 'chain', 'tags': [], 'metadata': {}, 'start_time': '2024-01-22T20:38:43.674+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableLambda\n", + "{'id': '9f343c6a-23f7-4a28-832f-d4fe3e95d1dc', 'name': 'RunnableLambda', 'type': 'chain', 'tags': ['map:key:agent_scratchpad'], 'metadata': {}, 'start_time': '2024-01-22T20:38:43.685+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableLambda/streamed_output/-\n", + "[]\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\":', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableParallel<agent_scratchpad>/streamed_output/-\n", + "{'agent_scratchpad': []}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableAssign<agent_scratchpad>/streamed_output/-\n", + "{'input': 'where is the cat hiding? what items are in that location?', 'intermediate_steps': [], 'agent_scratchpad': []}\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableLambda/end_time\n", + "2024-01-22T20:38:43.687+00:00\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableParallel<agent_scratchpad>/end_time\n", + "2024-01-22T20:38:43.688+00:00\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San', 'name': 'tavily_search_results_json'}}\n", + "/logs/RunnableAssign<agent_scratchpad>/end_time\n", + "2024-01-22T20:38:43.688+00:00\n", "----\n", - "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco', 'name': 'tavily_search_results_json'}}\n", + "/logs/ChatPromptTemplate\n", + "{'id': '7e3a84d5-46b8-4782-8eed-d1fe92be6a30', 'name': 'ChatPromptTemplate', 'type': 'prompt', 'tags': ['seq:step:2'], 'metadata': {}, 'start_time': '2024-01-22T20:38:43.689+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", + "----\n", + "/logs/ChatPromptTemplate/end_time\n", + "2024-01-22T20:38:43.689+00:00\n", + "----\n", + "/logs/ChatOpenAI\n", + "{'id': '6446f7ec-b3e4-4637-89d8-b4b34b46ea14', 'name': 'ChatOpenAI', 'type': 'llm', 'tags': ['seq:step:3', 'agent_llm'], 'metadata': {}, 'start_time': '2024-01-22T20:38:43.690+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n', 'name': 'tavily_search_results_json'}}\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_gKFg6FX8ZQ88wFUs94yx86PF', 'function': {'arguments': '', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}\n", "----\n", "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_gKFg6FX8ZQ88wFUs94yx86PF', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}\n", "----\n", "/logs/ChatOpenAI/streamed_output/-\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_gKFg6FX8ZQ88wFUs94yx86PF', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}\n", "----\n", "/logs/ChatOpenAI/end_time\n", - "2023-12-26T17:56:20.849\n", + "2024-01-22T20:38:44.203+00:00\n", "----\n", - "/final_output\n", - "None\n", + "/logs/OpenAIToolsAgentOutputParser\n", + "{'id': '65912835-8dcd-4be2-ad05-9f239a7ef704', 'name': 'OpenAIToolsAgentOutputParser', 'type': 'parser', 'tags': ['seq:step:4'], 'metadata': {}, 'start_time': '2024-01-22T20:38:44.204+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/final_output/messages/1\n", - "content='[{\"url\": \"https://weather.com/weather/tenday/l/San Francisco CA USCA0987:1:US\", \"content\": \"recents Specialty Forecasts 10 Day Weather-San Francisco, CA Today Mon 18 | Day Fri 22 Fri 22 | Day Foggy early, then partly cloudy later in the day. High around 60F. Winds W at 10 to 15 mph. Considerable cloudiness with occasional rain showers. High 59F. Winds SSE at 5 to 10 mph. Chance of rain 50%. Thu 28 | Night Cloudy with showers. Low 46F. Winds S at 5 to 10 mph. Chance of rain 40%. Fri 29 Fri 29 | Day10 Day Weather-San Francisco, CA As of 5:52 pm PST alertLevel3 Coastal Flood Advisory+1 More Tonight Mostly Cloudy Night --/44° Rain 7% Arrow Up Sun 24| Night 44° Mostly Cloudy Night Rain 7%...\"}]' name='tavily_search_results_json'\n", + "/logs/OpenAIToolsAgentOutputParser/end_time\n", + "2024-01-22T20:38:44.205+00:00\n", "----\n", - "/logs/ChatOpenAI:2\n", - "{'id': 'fc7ab413-6f59-4a9e-bae1-3140abdaff55', 'name': 'ChatOpenAI', 'type': 'llm', 'tags': ['seq:step:3'], 'metadata': {}, 'start_time': '2023-12-26T17:56:24.546', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", + "/logs/RunnableSequence/streamed_output/-\n", + "[OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_gKFg6FX8ZQ88wFUs94yx86PF', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_gKFg6FX8ZQ88wFUs94yx86PF')]\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content=''\n", + "/logs/RunnableSequence/end_time\n", + "2024-01-22T20:38:44.206+00:00\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently fog'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around '\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F.'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day,'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy.'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at '\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to '\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph.'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow,'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloud'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high'\n", - "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of'\n", + "/final_output\n", + "None\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of '\n", + "/logs/where_cat_is_hiding\n", + "{'id': '21fde139-0dfa-42bb-ad90-b5b1e984aaba', 'name': 'where_cat_is_hiding', 'type': 'tool', 'tags': [], 'metadata': {}, 'start_time': '2024-01-22T20:38:44.208+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 59'\n", + "/logs/where_cat_is_hiding/end_time\n", + "2024-01-22T20:38:44.208+00:00\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 59°F'\n", + "/final_output/messages/1\n", + "content='under the bed' name='where_cat_is_hiding'\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 59°F.'\n", + "/logs/RunnableSequence:2\n", + "{'id': '37d52845-b689-4c18-9c10-ffdd0c4054b0', 'name': 'RunnableSequence', 'type': 'chain', 'tags': [], 'metadata': {}, 'start_time': '2024-01-22T20:38:44.210+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI:2/streamed_output/-\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 59°F.'\n", + "/logs/RunnableAssign<agent_scratchpad>:2\n", + "{'id': '30024dea-064f-4b04-b130-671f47ac59bc', 'name': 'RunnableAssign<agent_scratchpad>', 'type': 'chain', 'tags': ['seq:step:1'], 'metadata': {}, 'start_time': '2024-01-22T20:38:44.213+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n", - "/logs/ChatOpenAI:2/end_time\n", - "2023-12-26T17:56:25.673\n", + "/logs/RunnableAssign<agent_scratchpad>:2/streamed_output/-\n", + "{'input': 'where is the cat hiding? what items are in that location?', 'intermediate_steps': [(OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_gKFg6FX8ZQ88wFUs94yx86PF', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_gKFg6FX8ZQ88wFUs94yx86PF'), 'under the bed')]}\n", "----\n", - "/final_output/messages/2\n", - "content='The weather in San Francisco is currently foggy with a high around 60°F. Later in the day, it will become partly cloudy. The wind is coming from the west at 10 to 15 mph. Tomorrow, there will be considerable cloudiness with occasional rain showers and a high of 59°F.'\n", + "/logs/RunnableParallel<agent_scratchpad>:2\n", + "{'id': '98906cd7-93c2-47e8-a7d7-2e8d4ab09ed0', 'name': 'RunnableParallel<agent_scratchpad>', 'type': 'chain', 'tags': [], 'metadata': {}, 'start_time': '2024-01-22T20:38:44.215+00:00', 'streamed_output': [], 'streamed_output_str': [], 'final_output': None, 'end_time': None}\n", "----\n" ] } ], "source": [ + "i = 0\n", "path_status = {}\n", "async for chunk in agent_executor.astream_log(\n", - " {\"input\": \"what is the weather in sf\", \"chat_history\": []},\n", - " include_names=[\"ChatOpenAI\"],\n", + " {\"input\": \"where is the cat hiding? what items are in that location?\"},\n", "):\n", " for op in chunk.ops:\n", " if op[\"op\"] == \"add\":\n", @@ -1071,16 +840,291 @@ " path_status[op[\"path\"]] += op[\"value\"]\n", " print(op[\"path\"])\n", " print(path_status.get(op[\"path\"]))\n", - " print(\"----\")" + " print(\"----\")\n", + " i += 1\n", + " if i > 30:\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "d85bf6ed-8d89-46fb-bbd8-6c84de7ae18f", + "metadata": {}, + "source": [ + "#### Using callbacks (Legacy)\n", + "\n", + "Another approach to streaming is using callbacks. This may be useful if you're still on an older version of LangChain and cannot upgrade.\n", + "\n", + "Generall, this is **NOT** a recommended approach because:\n", + "\n", + "1. for most applications, you'll need to create two workers, write the callbacks to a queue and have another worker reading from the queue (i.e., there's hidden complexity to make this work).\n", + "2. **end** events may be missing some metadata (e.g., like run name). So if you need the additional metadata, you should inherit from `BaseTracer` instead of `AsyncCallbackHandler` to pick up the relevant information from the runs (aka traces), or else implement the aggregation logic yourself based on the `run_id`.\n", + "3. There is inconsistent behavior with the callbacks (e.g., how inputs and outputs are encoded) depending on the callback type that you'll need to workaround.\n", + "\n", + "For illustration purposes, we implement a callback below that shows how to get *token by token* streaming. Feel free to implement other callbacks based on your application needs.\n", + "\n", + "But `astream_events` does all of this you under the hood, so you don't have to!" ] }, { "cell_type": "code", - "execution_count": null, - "id": "4fdfc76d", + "execution_count": 16, + "id": "2c577a4a-b754-4c32-a951-8003b876ea9a", "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "on chain start: \n", + "{'input': 'where is the cat hiding and what items can be found there?'}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "On chain end\n", + "[]\n", + "On chain end\n", + "{'agent_scratchpad': []}\n", + "On chain end\n", + "{'input': 'where is the cat hiding and what items can be found there?', 'intermediate_steps': [], 'agent_scratchpad': []}\n", + "on chain start: \n", + "{'input': 'where is the cat hiding and what items can be found there?', 'intermediate_steps': [], 'agent_scratchpad': []}\n", + "On chain end\n", + "{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'prompts', 'chat', 'ChatPromptValue'], 'kwargs': {'messages': [{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'SystemMessage'], 'kwargs': {'content': 'You are a helpful assistant', 'additional_kwargs': {}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'HumanMessage'], 'kwargs': {'content': 'where is the cat hiding and what items can be found there?', 'additional_kwargs': {}}}]}}\n", + "agent_llm: \n", + "\n", + "on chain start: \n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}\n", + "On chain end\n", + "[{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'agent', 'OpenAIToolAgentAction'], 'kwargs': {'tool': 'where_cat_is_hiding', 'tool_input': {}, 'log': '\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', 'message_log': [{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'example': False, 'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}}}], 'tool_call_id': 'call_pboyZTT0587rJtujUluO2OOc'}}]\n", + "On chain end\n", + "[OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_pboyZTT0587rJtujUluO2OOc')]\n", + "Tool start\n", + "{'name': 'where_cat_is_hiding', 'description': 'where_cat_is_hiding() -> str - Where is the cat hiding right now?'}\n", + "Tool end\n", + "on the shelf\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "On chain end\n", + "[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc')]\n", + "On chain end\n", + "{'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc')]}\n", + "On chain end\n", + "{'input': 'where is the cat hiding and what items can be found there?', 'intermediate_steps': [(OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), 'on the shelf')], 'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc')]}\n", + "on chain start: \n", + "{'input': 'where is the cat hiding and what items can be found there?', 'intermediate_steps': [(OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), 'on the shelf')], 'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc')]}\n", + "On chain end\n", + "{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'prompts', 'chat', 'ChatPromptValue'], 'kwargs': {'messages': [{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'SystemMessage'], 'kwargs': {'content': 'You are a helpful assistant', 'additional_kwargs': {}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'HumanMessage'], 'kwargs': {'content': 'where is the cat hiding and what items can be found there?', 'additional_kwargs': {}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'example': False, 'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'ToolMessage'], 'kwargs': {'tool_call_id': 'call_pboyZTT0587rJtujUluO2OOc', 'content': 'on the shelf', 'additional_kwargs': {'name': 'where_cat_is_hiding'}}}]}}\n", + "agent_llm: \n", + "\n", + "on chain start: \n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}\n", + "On chain end\n", + "[{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'agent', 'OpenAIToolAgentAction'], 'kwargs': {'tool': 'get_items', 'tool_input': {'place': 'shelf'}, 'log': \"\\nInvoking: `get_items` with `{'place': 'shelf'}`\\n\\n\\n\", 'message_log': [{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'example': False, 'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}}}], 'tool_call_id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh'}}]\n", + "On chain end\n", + "[OpenAIToolAgentAction(tool='get_items', tool_input={'place': 'shelf'}, log=\"\\nInvoking: `get_items` with `{'place': 'shelf'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]})], tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh')]\n", + "Tool start\n", + "{'name': 'get_items', 'description': 'get_items(place: str, callbacks: Union[List[langchain_core.callbacks.base.BaseCallbackHandler], langchain_core.callbacks.base.BaseCallbackManager, NoneType]) -> str - Use this tool to look up which items are in the given place.'}\n", + "tool_llm: In| a| shelf|,| you| might| find|:\n", + "\n", + "|1|.| Books|:| A| shelf| is| commonly| used| to| store| books|.| Books| can| be| of| various| genres|,| such| as| novels|,| textbooks|,| or| reference| books|.| They| provide| knowledge|,| entertainment|,| and| can| transport| you| to| different| worlds| through| storytelling|.\n", + "\n", + "|2|.| Decor|ative| items|:| Sh|elves| often| serve| as| a| display| area| for| decorative| items| like| figur|ines|,| v|ases|,| or| sculptures|.| These| items| add| aesthetic| value| to| the| space| and| reflect| the| owner|'s| personal| taste| and| style|.\n", + "\n", + "|3|.| Storage| boxes|:| Sh|elves| can| also| be| used| to| store| various| items| in| organized| boxes|.| These| boxes| can| hold| anything| from| office| supplies|,| craft| materials|,| or| sentimental| items|.| They| help| keep| the| space| tidy| and| provide| easy| access| to| stored| belongings|.|\n", + "\n", + "Tool end\n", + "In a shelf, you might find:\n", + "\n", + "1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\n", + "\n", + "2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\n", + "\n", + "3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "on chain start: \n", + "{'input': ''}\n", + "On chain end\n", + "[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}), ToolMessage(content=\"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\", additional_kwargs={'name': 'get_items'}, tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh')]\n", + "On chain end\n", + "{'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}), ToolMessage(content=\"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\", additional_kwargs={'name': 'get_items'}, tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh')]}\n", + "On chain end\n", + "{'input': 'where is the cat hiding and what items can be found there?', 'intermediate_steps': [(OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), 'on the shelf'), (OpenAIToolAgentAction(tool='get_items', tool_input={'place': 'shelf'}, log=\"\\nInvoking: `get_items` with `{'place': 'shelf'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]})], tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh'), \"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\")], 'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}), ToolMessage(content=\"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\", additional_kwargs={'name': 'get_items'}, tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh')]}\n", + "on chain start: \n", + "{'input': 'where is the cat hiding and what items can be found there?', 'intermediate_steps': [(OpenAIToolAgentAction(tool='where_cat_is_hiding', tool_input={}, log='\\nInvoking: `where_cat_is_hiding` with `{}`\\n\\n\\n', message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]})], tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), 'on the shelf'), (OpenAIToolAgentAction(tool='get_items', tool_input={'place': 'shelf'}, log=\"\\nInvoking: `get_items` with `{'place': 'shelf'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]})], tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh'), \"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\")], 'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}), ToolMessage(content='on the shelf', additional_kwargs={'name': 'where_cat_is_hiding'}, tool_call_id='call_pboyZTT0587rJtujUluO2OOc'), AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}), ToolMessage(content=\"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\", additional_kwargs={'name': 'get_items'}, tool_call_id='call_vIVtgUb9Gvmc3zAGIrshnmbh')]}\n", + "On chain end\n", + "{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'prompts', 'chat', 'ChatPromptValue'], 'kwargs': {'messages': [{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'SystemMessage'], 'kwargs': {'content': 'You are a helpful assistant', 'additional_kwargs': {}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'HumanMessage'], 'kwargs': {'content': 'where is the cat hiding and what items can be found there?', 'additional_kwargs': {}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'example': False, 'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_pboyZTT0587rJtujUluO2OOc', 'function': {'arguments': '{}', 'name': 'where_cat_is_hiding'}, 'type': 'function'}]}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'ToolMessage'], 'kwargs': {'tool_call_id': 'call_pboyZTT0587rJtujUluO2OOc', 'content': 'on the shelf', 'additional_kwargs': {'name': 'where_cat_is_hiding'}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'example': False, 'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'function': {'arguments': '{\\n \"place\": \"shelf\"\\n}', 'name': 'get_items'}, 'type': 'function'}]}}}, {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'ToolMessage'], 'kwargs': {'tool_call_id': 'call_vIVtgUb9Gvmc3zAGIrshnmbh', 'content': \"In a shelf, you might find:\\n\\n1. Books: A shelf is commonly used to store books. Books can be of various genres, such as novels, textbooks, or reference books. They provide knowledge, entertainment, and can transport you to different worlds through storytelling.\\n\\n2. Decorative items: Shelves often serve as a display area for decorative items like figurines, vases, or sculptures. These items add aesthetic value to the space and reflect the owner's personal taste and style.\\n\\n3. Storage boxes: Shelves can also be used to store various items in organized boxes. These boxes can hold anything from office supplies, craft materials, or sentimental items. They help keep the space tidy and provide easy access to stored belongings.\", 'additional_kwargs': {'name': 'get_items'}}}]}}\n", + "agent_llm: The| cat| is| hiding| on| the| shelf|.| In| the| shelf|,| you| might| find| books|,| decorative| items|,| and| storage| boxes|.|\n", + "\n", + "on chain start: \n", + "content='The cat is hiding on the shelf. In the shelf, you might find books, decorative items, and storage boxes.'\n", + "On chain end\n", + "{'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'agent', 'AgentFinish'], 'kwargs': {'return_values': {'output': 'The cat is hiding on the shelf. In the shelf, you might find books, decorative items, and storage boxes.'}, 'log': 'The cat is hiding on the shelf. In the shelf, you might find books, decorative items, and storage boxes.'}}\n", + "On chain end\n", + "return_values={'output': 'The cat is hiding on the shelf. In the shelf, you might find books, decorative items, and storage boxes.'} log='The cat is hiding on the shelf. In the shelf, you might find books, decorative items, and storage boxes.'\n", + "On chain end\n", + "{'output': 'The cat is hiding on the shelf. In the shelf, you might find books, decorative items, and storage boxes.'}\n" + ] + } + ], + "source": [ + "from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, TypeVar, Union\n", + "from uuid import UUID\n", + "\n", + "from langchain_core.callbacks.base import AsyncCallbackHandler\n", + "from langchain_core.messages import BaseMessage\n", + "from langchain_core.outputs import ChatGenerationChunk, GenerationChunk, LLMResult\n", + "\n", + "# Here is a custom handler that will print the tokens to stdout.\n", + "# Instead of printing to stdout you can send the data elsewhere; e.g., to a streaming API response\n", + "\n", + "\n", + "class TokenByTokenHandler(AsyncCallbackHandler):\n", + " def __init__(self, tags_of_interest: List[str]) -> None:\n", + " \"\"\"A custom call back handler.\n", + "\n", + " Args:\n", + " tags_of_interest: Only LLM tokens from models with these tags will be\n", + " printed.\n", + " \"\"\"\n", + " self.tags_of_interest = tags_of_interest\n", + "\n", + " async def on_chain_start(\n", + " self,\n", + " serialized: Dict[str, Any],\n", + " inputs: Dict[str, Any],\n", + " *,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " tags: Optional[List[str]] = None,\n", + " metadata: Optional[Dict[str, Any]] = None,\n", + " **kwargs: Any,\n", + " ) -> None:\n", + " \"\"\"Run when chain starts running.\"\"\"\n", + " print(\"on chain start: \")\n", + " print(inputs)\n", + "\n", + " async def on_chain_end(\n", + " self,\n", + " outputs: Dict[str, Any],\n", + " *,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " tags: Optional[List[str]] = None,\n", + " **kwargs: Any,\n", + " ) -> None:\n", + " \"\"\"Run when chain ends running.\"\"\"\n", + " print(\"On chain end\")\n", + " print(outputs)\n", + "\n", + " async def on_chat_model_start(\n", + " self,\n", + " serialized: Dict[str, Any],\n", + " messages: List[List[BaseMessage]],\n", + " *,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " tags: Optional[List[str]] = None,\n", + " metadata: Optional[Dict[str, Any]] = None,\n", + " **kwargs: Any,\n", + " ) -> Any:\n", + " \"\"\"Run when a chat model starts running.\"\"\"\n", + " overlap_tags = self.get_overlap_tags(tags)\n", + "\n", + " if overlap_tags:\n", + " print(\",\".join(overlap_tags), end=\": \", flush=True)\n", + "\n", + " def on_tool_start(\n", + " self,\n", + " serialized: Dict[str, Any],\n", + " input_str: str,\n", + " *,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " tags: Optional[List[str]] = None,\n", + " metadata: Optional[Dict[str, Any]] = None,\n", + " inputs: Optional[Dict[str, Any]] = None,\n", + " **kwargs: Any,\n", + " ) -> Any:\n", + " \"\"\"Run when tool starts running.\"\"\"\n", + " print(\"Tool start\")\n", + " print(serialized)\n", + "\n", + " def on_tool_end(\n", + " self,\n", + " output: str,\n", + " *,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " **kwargs: Any,\n", + " ) -> Any:\n", + " \"\"\"Run when tool ends running.\"\"\"\n", + " print(\"Tool end\")\n", + " print(output)\n", + "\n", + " async def on_llm_end(\n", + " self,\n", + " response: LLMResult,\n", + " *,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " tags: Optional[List[str]] = None,\n", + " **kwargs: Any,\n", + " ) -> None:\n", + " \"\"\"Run when LLM ends running.\"\"\"\n", + " overlap_tags = self.get_overlap_tags(tags)\n", + "\n", + " if overlap_tags:\n", + " # Who can argue with beauty?\n", + " print()\n", + " print()\n", + "\n", + " def get_overlap_tags(self, tags: Optional[List[str]]) -> List[str]:\n", + " \"\"\"Check for overlap with filtered tags.\"\"\"\n", + " if not tags:\n", + " return []\n", + " return sorted(set(tags or []) & set(self.tags_of_interest or []))\n", + "\n", + " async def on_llm_new_token(\n", + " self,\n", + " token: str,\n", + " *,\n", + " chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,\n", + " run_id: UUID,\n", + " parent_run_id: Optional[UUID] = None,\n", + " tags: Optional[List[str]] = None,\n", + " **kwargs: Any,\n", + " ) -> None:\n", + " \"\"\"Run on new LLM token. Only available when streaming is enabled.\"\"\"\n", + " overlap_tags = self.get_overlap_tags(tags)\n", + "\n", + " if token and overlap_tags:\n", + " print(token, end=\"|\", flush=True)\n", + "\n", + "\n", + "handler = TokenByTokenHandler(tags_of_interest=[\"tool_llm\", \"agent_llm\"])\n", + "\n", + "result = await agent_executor.ainvoke(\n", + " {\"input\": \"where is the cat hiding and what items can be found there?\"},\n", + " {\"callbacks\": [handler]},\n", + ")" + ] } ], "metadata": { @@ -1099,7 +1143,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/docs/docs/modules/agents/how_to/streaming_events.ipynb b/docs/docs/modules/agents/how_to/streaming_events.ipynb deleted file mode 100644 index 4f1ad14a374ba..0000000000000 --- a/docs/docs/modules/agents/how_to/streaming_events.ipynb +++ /dev/null @@ -1,350 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "b69e747b-4e79-4caf-8f8b-c6e70275a31d", - "metadata": {}, - "source": [ - "# Event Streaming\n", - "\n", - "**NEW** This is a new API only works with recent versions of langchain-core!\n", - "\n", - "In this notebook, we'll see how to use `astream_events` to stream **token by token** from LLM calls used within the tools invoked by the agent. \n", - "\n", - "We will **only** stream tokens from LLMs used within tools and from no other LLMs (just to show that we can)! \n", - "\n", - "Feel free to adapt this example to the needs of your application.\n", - "\n", - "Our agent will use the OpenAI tools API for tool invocation, and we'll provide the agent with two tools:\n", - "\n", - "1. `where_cat_is_hiding`: A tool that uses an LLM to tell us where the cat is hiding\n", - "2. `tell_me_a_joke_about`: A tool that can use an LLM to tell a joke about the given topic\n", - "\n", - "\n", - "## ⚠️ Beta API ⚠️ ##\n", - "\n", - "Event Streaming is a **beta** API, and may change a bit based on feedback.\n", - "\n", - "Keep in mind the following constraints (repeated in tools section):\n", - "\n", - "* streaming only works properly if using `async`\n", - "* propagate callbacks if definning custom functions / runnables\n", - "* If creating a tool that uses an LLM, make sure to use `.astream()` on the LLM rather than `.ainvoke` to ask the LLM to stream tokens.\n", - "\n", - "## Event Hooks Reference\n", - "\n", - "\n", - "Here is a reference table that shows some events that might be emitted by the various Runnable objects.\n", - "Definitions for some of the Runnable are included after the table.\n", - "\n", - "⚠️ When streaming the inputs for the runnable will not be available until the input stream has been entirely consumed This means that the inputs will be available at for the corresponding `end` hook rather than `start` event.\n", - "\n", - "\n", - "| event | name | chunk | input | output |\n", - "|----------------------|------------------|---------------------------------|-----------------------------------------------|-------------------------------------------------|\n", - "| on_chat_model_start | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | |\n", - "| on_chat_model_stream | [model name] | AIMessageChunk(content=\"hello\") | | |\n", - "| on_chat_model_end | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | {\"generations\": [...], \"llm_output\": None, ...} |\n", - "| on_llm_start | [model name] | | {'input': 'hello'} | |\n", - "| on_llm_stream | [model name] | 'Hello' | | |\n", - "| on_llm_end | [model name] | | 'Hello human!' |\n", - "| on_chain_start | format_docs | | | |\n", - "| on_chain_stream | format_docs | \"hello world!, goodbye world!\" | | |\n", - "| on_chain_end | format_docs | | [Document(...)] | \"hello world!, goodbye world!\" |\n", - "| on_tool_start | some_tool | | {\"x\": 1, \"y\": \"2\"} | |\n", - "| on_tool_stream | some_tool | {\"x\": 1, \"y\": \"2\"} | | |\n", - "| on_tool_end | some_tool | | | {\"x\": 1, \"y\": \"2\"} |\n", - "| on_retriever_start | [retriever name] | | {\"query\": \"hello\"} | |\n", - "| on_retriever_chunk | [retriever name] | {documents: [...]} | | |\n", - "| on_retriever_end | [retriever name] | | {\"query\": \"hello\"} | {documents: [...]} |\n", - "| on_prompt_start | [template_name] | | {\"question\": \"hello\"} | |\n", - "| on_prompt_end | [template_name] | | {\"question\": \"hello\"} | ChatPromptValue(messages: [SystemMessage, ...]) |\n", - "\n", - "\n", - "Here are declarations associated with the events shown above:\n", - "\n", - "`format_docs`:\n", - "\n", - "```python\n", - "def format_docs(docs: List[Document]) -> str:\n", - " '''Format the docs.'''\n", - " return \", \".join([doc.page_content for doc in docs])\n", - "\n", - "format_docs = RunnableLambda(format_docs)\n", - "```\n", - "\n", - "`some_tool`:\n", - "\n", - "```python\n", - "@tool\n", - "def some_tool(x: int, y: str) -> dict:\n", - " '''Some_tool.'''\n", - " return {\"x\": x, \"y\": y}\n", - "```\n", - "\n", - "`prompt`:\n", - "\n", - "```python\n", - "template = ChatPromptTemplate.from_messages(\n", - " [(\"system\", \"You are Cat Agent 007\"), (\"human\", \"{question}\")]\n", - ").with_config({\"run_name\": \"my_template\", \"tags\": [\"my_template\"]})\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "29205bef-2288-48e9-9067-f19072277a97", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "from langchain import hub\n", - "from langchain.agents import AgentExecutor, create_openai_tools_agent\n", - "from langchain.tools import tool\n", - "from langchain_core.callbacks import Callbacks\n", - "from langchain_core.prompts import ChatPromptTemplate\n", - "from langchain_openai import ChatOpenAI" - ] - }, - { - "cell_type": "markdown", - "id": "d6b0fafa-ce3b-489b-bf1d-d37b87f4819e", - "metadata": {}, - "source": [ - "## Create the model\n", - "\n", - "**Attention** For older versions of langchain, we must set `streaming=True`" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "fa3c3761-a1cd-4118-8559-ea4d8857d394", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "model = ChatOpenAI(temperature=0, streaming=True)" - ] - }, - { - "cell_type": "markdown", - "id": "b76e1a3b-2983-42d9-ac12-4a0f32cd4a24", - "metadata": {}, - "source": [ - "## Tools\n", - "\n", - "We define two tools that rely on a chat model to generate output!\n", - "\n", - "Please note a few different things:\n", - "\n", - "1. The tools are **async**\n", - "1. The model is invoked using **.astream()** to force the output to stream\n", - "1. For older langchain versions you should set `streaming=True` on the model!\n", - "1. We attach tags to the model so that we can filter on said tags in our callback handler\n", - "1. The tools accept callbacks and propagate them to the model as a runtime argument" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c767f760-fe52-47e5-9c2a-622f03507aaf", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "@tool\n", - "async def where_cat_is_hiding(callbacks: Callbacks) -> str: # <--- Accept callbacks\n", - " \"\"\"Where is the cat hiding right now?\"\"\"\n", - " chunks = [\n", - " chunk\n", - " async for chunk in model.astream(\n", - " \"Give one up to three word answer about where the cat might be hiding in the house right now.\",\n", - " {\n", - " \"tags\": [\"tool_llm\"],\n", - " \"callbacks\": callbacks,\n", - " }, # <--- Propagate callbacks and assign a tag to this model\n", - " )\n", - " ]\n", - " return \"\".join(chunk.content for chunk in chunks)\n", - "\n", - "\n", - "@tool\n", - "async def tell_me_a_joke_about(\n", - " topic: str, callbacks: Callbacks\n", - ") -> str: # <--- Accept callbacks\n", - " \"\"\"Tell a joke about a given topic.\"\"\"\n", - " template = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\"system\", \"You are Cat Agent 007. You are funny and know many jokes.\"),\n", - " (\"human\", \"Tell me a long joke about {topic}\"),\n", - " ]\n", - " )\n", - " chain = template | model.with_config({\"tags\": [\"tool_llm\"]})\n", - " chunks = [\n", - " chunk\n", - " async for chunk in chain.astream({\"topic\": topic}, {\"callbacks\": callbacks})\n", - " ]\n", - " return \"\".join(chunk.content for chunk in chunks)" - ] - }, - { - "cell_type": "markdown", - "id": "cba476f8-29da-4c2c-9134-186871caf7ae", - "metadata": {}, - "source": [ - "## Initialize the Agent" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "0bab4488-bf4c-461f-b41e-5e60310fe0f2", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "input_variables=['agent_scratchpad', 'input'] input_types={'chat_history': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]], 'agent_scratchpad': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]} messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')), MessagesPlaceholder(variable_name='chat_history', optional=True), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')), MessagesPlaceholder(variable_name='agent_scratchpad')]\n", - "[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')), MessagesPlaceholder(variable_name='chat_history', optional=True), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')), MessagesPlaceholder(variable_name='agent_scratchpad')]\n" - ] - } - ], - "source": [ - "# Get the prompt to use - you can modify this!\n", - "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", - "print(prompt)\n", - "print(prompt.messages)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "1762f4e1-402a-4bfb-af26-eb5b7b8f56bd", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "tools = [tell_me_a_joke_about, where_cat_is_hiding]\n", - "agent = create_openai_tools_agent(model.with_config({\"tags\": [\"agent\"]}), tools, prompt)\n", - "executor = AgentExecutor(agent=agent, tools=tools)" - ] - }, - { - "cell_type": "markdown", - "id": "841271d7-1de1-41a9-9387-bb04368537f1", - "metadata": {}, - "source": [ - "## Stream the output\n", - "\n", - "The streamed output is shown with a `|` as the delimiter between tokens. " - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a5d94bd8-4a55-4527-b21a-4245a38c7c26", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/eugene/src/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: This API is in beta and may change in the future.\n", - " warn_beta(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "--\n", - "Starting tool: where_cat_is_hiding with inputs: {}\n", - "\n", - "\n", - "|Under| the| bed|.||\n", - "\n", - "Ended tool: where_cat_is_hiding\n", - "--\n", - "Starting tool: tell_me_a_joke_about with inputs: {'topic': 'under the bed'}\n", - "\n", - "\n", - "|Sure|,| here|'s| a| long| joke| about| what|'s| hiding| under| the| bed|:\n", - "\n", - "|Once| upon| a| time|,| there| was| a| mis|chie|vous| little| boy| named| Tim|my|.| Tim|my| had| always| been| afraid| of| what| might| be| lurking| under| his| bed| at| night|.| Every| evening|,| he| would| ti|pt|oe| into| his| room|,| turn| off| the| lights|,| and| then| make| a| daring| leap| onto| his| bed|,| ensuring| that| nothing| could| grab| his| ankles|.\n", - "\n", - "|One| night|,| Tim|my|'s| parents| decided| to| play| a| prank| on| him|.| They| hid| a| remote|-controlled| toy| monster| under| his| bed|,| complete| with| glowing| eyes| and| a| grow|ling| sound| effect|.| As| Tim|my| settled| into| bed|,| his| parents| quietly| sn|uck| into| his| room|,| ready| to| give| him| the| scare| of| a| lifetime|.\n", - "\n", - "|Just| as| Tim|my| was| about| to| drift| off| to| sleep|,| he| heard| a| faint| grow|l| coming| from| under| his| bed|.| His| eyes| widened| with| fear|,| and| his| heart| started| racing|.| He| must|ered| up| the| courage| to| peek| under| the| bed|,| and| to| his| surprise|,| he| saw| a| pair| of| glowing| eyes| staring| back| at| him|.\n", - "\n", - "|Terr|ified|,| Tim|my| jumped| out| of| bed| and| ran| to| his| parents|,| screaming|,| \"|There|'s| a| monster| under| my| bed|!| Help|!\"\n", - "\n", - "|His| parents|,| trying| to| st|ifle| their| laughter|,| rushed| into| his| room|.| They| pretended| to| be| just| as| scared| as| Tim|my|,| and| together|,| they| brav|ely| approached| the| bed|.| Tim|my|'s| dad| grabbed| a| bro|om|stick|,| ready| to| defend| his| family| against| the| imaginary| monster|.\n", - "\n", - "|As| they| got| closer|,| the| \"|monster|\"| under| the| bed| started| to| move|.| Tim|my|'s| mom|,| unable| to| contain| her| laughter| any| longer|,| pressed| a| button| on| the| remote| control|,| causing| the| toy| monster| to| sc|urry| out| from| under| the| bed|.| Tim|my|'s| fear| quickly| turned| into| confusion|,| and| then| into| laughter| as| he| realized| it| was| all| just| a| prank|.\n", - "\n", - "|From| that| day| forward|,| Tim|my| learned| that| sometimes| the| things| we| fear| the| most| are| just| fig|ments| of| our| imagination|.| And| as| for| what|'s| hiding| under| his| bed|?| Well|,| it|'s| just| dust| b|unn|ies| and| the| occasional| missing| sock|.| Nothing| to| be| afraid| of|!\n", - "\n", - "|Remember|,| laughter| is| the| best| monster| repell|ent|!||\n", - "\n", - "Ended tool: tell_me_a_joke_about\n" - ] - } - ], - "source": [ - "async for event in executor.astream_events(\n", - " {\"input\": \"where is the cat hiding? Tell me a joke about that location?\"},\n", - " include_tags=[\"tool_llm\"],\n", - " include_types=[\"tool\"],\n", - "):\n", - " hook = event[\"event\"]\n", - " if hook == \"on_chat_model_stream\":\n", - " print(event[\"data\"][\"chunk\"].content, end=\"|\")\n", - " elif hook in {\"on_chat_model_start\", \"on_chat_model_end\"}:\n", - " print()\n", - " print()\n", - " elif hook == \"on_tool_start\":\n", - " print(\"--\")\n", - " print(\n", - " f\"Starting tool: {event['name']} with inputs: {event['data'].get('input')}\"\n", - " )\n", - " elif hook == \"on_tool_end\":\n", - " print(f\"Ended tool: {event['name']}\")\n", - " else:\n", - " pass" - ] - } - ], - "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.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From c98994c3c99679ffd6d61b1c6aefc27c8d1744d0 Mon Sep 17 00:00:00 2001 From: Ian <ArGregoryIan@gmail.com> Date: Tue, 23 Jan 2024 13:58:37 +0800 Subject: [PATCH 175/215] docs: Improve notebook to show how to use tidb to store history messages (#16420) After merging [PR #16304](https://github.com/langchain-ai/langchain/pull/16304), I realized that our notebook example for integrating TiDB with LangChain was too basic. To make it more useful and user-friendly, I plan to create a detailed example. This will show how to use TiDB for saving history messages in LangChain, offering a clearer, more practical guide for our users --- .../memory/tidb_chat_message_history.ipynb | 203 +++++++++++++++++- 1 file changed, 196 insertions(+), 7 deletions(-) diff --git a/docs/docs/integrations/memory/tidb_chat_message_history.ipynb b/docs/docs/integrations/memory/tidb_chat_message_history.ipynb index 8a49af973d8fc..df3bc3da22786 100644 --- a/docs/docs/integrations/memory/tidb_chat_message_history.ipynb +++ b/docs/docs/integrations/memory/tidb_chat_message_history.ipynb @@ -11,44 +11,233 @@ "This notebook introduces how to use TiDB to store chat message history. " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Firstly, we will install the following dependencies:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain_openai" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Configuring your OpenAI Key" + ] + }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Input your OpenAI API key:\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we will configure the connection to a TiDB. In this notebook, we will follow the standard connection method provided by TiDB Cloud to establish a secure and efficient database connection." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# copy from tidb cloud console\n", + "tidb_connection_string_template = \"mysql+pymysql://<USER>:<PASSWORD>@<HOST>:4000/<DB>?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true\"\n", + "tidb_password = getpass.getpass(\"Input your TiDB password:\")\n", + "tidb_connection_string = tidb_connection_string_template.replace(\n", + " \"<PASSWORD>\", tidb_password\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generating historical data\n", + "\n", + "Creating a set of historical data, which will serve as the foundation for our upcoming demonstrations." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], "source": [ "from datetime import datetime\n", "\n", "from langchain_community.chat_message_histories import TiDBChatMessageHistory\n", "\n", "history = TiDBChatMessageHistory(\n", - " connection_string=\"mysql+pymysql://<host>:<PASSWORD>@<host>:4000/<db>?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true\",\n", + " connection_string=tidb_connection_string,\n", " session_id=\"code_gen\",\n", " earliest_time=datetime.utcnow(), # Optional to set earliest_time to load messages after this time point.\n", ")\n", "\n", - "history.add_user_message(\"hi! How's feature going?\")\n", - "history.add_ai_message(\"It's almot done\")" + "history.add_user_message(\"How's our feature going?\")\n", + "history.add_ai_message(\n", + " \"It's going well. We are working on testing now. It will be released in Feb.\"\n", + ")" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[HumanMessage(content=\"How's our feature going?\"),\n", + " AIMessage(content=\"It's going well. We are working on testing now. It will be released in Feb.\")]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "history.messages" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Chatting with historical data\n", + "\n", + "Let’s build upon the historical data generated earlier to create a dynamic chat interaction. \n", + "\n", + "Firstly, Creating a Chat Chain with LangChain:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You're an assistant who's good at coding. You're helping a startup build\",\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"history\"),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "chain = prompt | ChatOpenAI()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Building a Runnable on History:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "\n", + "chain_with_history = RunnableWithMessageHistory(\n", + " chain,\n", + " lambda session_id: TiDBChatMessageHistory(\n", + " session_id=session_id, connection_string=tidb_connection_string\n", + " ),\n", + " input_messages_key=\"question\",\n", + " history_messages_key=\"history\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Initiating the Chat:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='There are 31 days in January, so there are 30 days until our feature is released in February.')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response = chain_with_history.invoke(\n", + " {\"question\": \"Today is Jan 1st. How many days until our feature is released?\"},\n", + " config={\"configurable\": {\"session_id\": \"code_gen\"}},\n", + ")\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Checking the history data" + ] + }, + { + "cell_type": "code", + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[HumanMessage(content=\"hi! How's feature going?\"),\n", - " AIMessage(content=\"It's almot done\")]" + "[HumanMessage(content=\"How's our feature going?\"),\n", + " AIMessage(content=\"It's going well. We are working on testing now. It will be released in Feb.\"),\n", + " HumanMessage(content='Today is Jan 1st. How many days until our feature is released?'),\n", + " AIMessage(content='There are 31 days in January, so there are 30 days until our feature is released in February.')]" ] }, - "execution_count": 3, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "history.reload_cache()\n", "history.messages" ] } From 3b0226b2c64d144f8e6cdea52bd3f80b87ffbd86 Mon Sep 17 00:00:00 2001 From: Michael Gorham <michael@conexed.com> Date: Mon, 22 Jan 2024 22:59:59 -0700 Subject: [PATCH 176/215] docs: Update redis_chat_message_history.ipynb (#16344) ## Problem Spent several hours trying to figure out how to pass `RedisChatMessageHistory` as a `GetSessionHistoryCallable` with a different REDIS hostname. This example kept connecting to `redis://localhost:6379`, but I wanted to connect to a server not hosted locally. ## Cause Assumption the user knows how to implement `BaseChatMessageHistory` and `GetSessionHistoryCallable` ## Solution Update documentation to show how to explicitly set the REDIS hostname using a lambda function much like the MongoDB and SQLite examples. --- .../docs/integrations/memory/redis_chat_message_history.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/integrations/memory/redis_chat_message_history.ipynb b/docs/docs/integrations/memory/redis_chat_message_history.ipynb index 0b68c886414b1..3e79998e729bd 100644 --- a/docs/docs/integrations/memory/redis_chat_message_history.ipynb +++ b/docs/docs/integrations/memory/redis_chat_message_history.ipynb @@ -139,7 +139,9 @@ "\n", "chain_with_history = RunnableWithMessageHistory(\n", " chain,\n", - " RedisChatMessageHistory,\n", + " lambda session_id: RedisChatMessageHistory(\n", + " session_id, url=\"redis://localhost:6379\"\n", + " ),\n", " input_messages_key=\"question\",\n", " history_messages_key=\"history\",\n", ")\n", From fb41b68ea1a82cb08f8997050d093aa087c7b4d7 Mon Sep 17 00:00:00 2001 From: KhoPhi <seanmavley@gmail.com> Date: Tue, 23 Jan 2024 06:05:59 +0000 Subject: [PATCH 177/215] docs: Update with LCEL examples to Ollama & ChatOllama Integration notebook (#16194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Description:** Updated the Chat/Ollama docs notebook with LCEL chain examples - **Issue:** #15664 I'm a new contributor 😊 - **Dependencies:** No dependencies - **Twitter handle:** Comments: - How do I truncate the output of the stream in the notebook if and or when it goes on and on and on for even the basic of prompts? Edit: Looking forward to feedback @baskaryan --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- docs/docs/integrations/chat/ollama.ipynb | 381 +++++++++++++++-------- docs/docs/integrations/llms/ollama.ipynb | 180 ++++++++--- docs/static/img/ollama_example_img.jpg | Bin 0 -> 65920 bytes 3 files changed, 387 insertions(+), 174 deletions(-) create mode 100644 docs/static/img/ollama_example_img.jpg diff --git a/docs/docs/integrations/chat/ollama.ipynb b/docs/docs/integrations/chat/ollama.ipynb index 054666d807671..d0df5b4b99d8a 100644 --- a/docs/docs/integrations/chat/ollama.ipynb +++ b/docs/docs/integrations/chat/ollama.ipynb @@ -15,105 +15,231 @@ "source": [ "# ChatOllama\n", "\n", - "[Ollama](https://ollama.ai/) allows you to run open-source large language models, such as LLaMA2, locally.\n", + "[Ollama](https://ollama.ai/) allows you to run open-source large language models, such as Llama 2, locally.\n", "\n", "Ollama bundles model weights, configuration, and data into a single package, defined by a Modelfile. \n", "\n", "It optimizes setup and configuration details, including GPU usage.\n", "\n", - "For a complete list of supported models and model variants, see the [Ollama model library](https://ollama.ai/library).\n", + "For a complete list of supported models and model variants, see the [Ollama model library](https://github.com/jmorganca/ollama#model-library).\n", "\n", "## Setup\n", "\n", "First, follow [these instructions](https://github.com/jmorganca/ollama) to set up and run a local Ollama instance:\n", "\n", - "* [Download](https://ollama.ai/download)\n", - "* Fetch a model via `ollama pull <model family>`\n", - "* e.g., for `Llama-7b`: `ollama pull llama2`\n", - "* This will download the most basic version of the model (e.g., minimum # parameters and 4-bit quantization)\n", - "* On Mac, it will download to:\n", + "* [Download](https://ollama.ai/download) and install Ollama onto the available supported platforms (including Windows Subsystem for Linux)\n", + "* Fetch available LLM model via `ollama pull <name-of-model>`\n", + " * View a list of available models via the [model library](https://ollama.ai/library)\n", + " * e.g., for `Llama-7b`: `ollama pull llama2`\n", + "* This will download the default tagged version of the model. Typically, the default points to the latest, smallest sized-parameter model.\n", "\n", - "`~/.ollama/models/manifests/registry.ollama.ai/library/<model family>/latest`\n", + "> On Mac, the models will be download to `~/.ollama/models`\n", + "> \n", + "> On Linux (or WSL), the models will be stored at `/usr/share/ollama/.ollama/models`\n", "\n", - "* And we can specify a particular version, e.g., for `ollama pull vicuna:13b-v1.5-16k-q4_0`\n", - "* The file is here with the model version in place of `latest`\n", + "* Specify the exact version of the model of interest as such `ollama pull vicuna:13b-v1.5-16k-q4_0` (View the [various tags for the `Vicuna`](https://ollama.ai/library/vicuna/tags) model in this instance)\n", + "* To view all pulled models, use `ollama list`\n", + "* To chat directly with a model from the command line, use `ollama run <name-of-model>`\n", + "* View the [Ollama documentation](https://github.com/jmorganca/ollama) for more commands. Run `ollama help` in the terminal to see available commands too.\n", "\n", - "`~/.ollama/models/manifests/registry.ollama.ai/library/vicuna/13b-v1.5-16k-q4_0`\n", + "## Usage\n", "\n", - "You can easily access models in a few ways:\n", + "You can see a full list of supported parameters on the [API reference page](https://api.python.langchain.com/en/latest/llms/langchain.llms.ollama.Ollama.html).\n", "\n", - "1/ if the app is running:\n", + "If you are using a LLaMA `chat` model (e.g., `ollama pull llama2:7b-chat`) then you can use the `ChatOllama` interface.\n", "\n", - "* All of your local models are automatically served on `localhost:11434`\n", - "* Select your model when setting `llm = Ollama(..., model=\"<model family>:<version>\")`\n", - "* If you set `llm = Ollama(..., model=\"<model family\")` withoout a version it will simply look for `latest`\n", + "This includes [special tokens](https://huggingface.co/blog/llama2#how-to-prompt-llama-2) for system message and user input.\n", "\n", - "2/ if building from source or just running the binary: \n", + "## Interacting with Models \n", + "\n", + "Here are a few ways to interact with pulled local models\n", + "\n", + "#### directly in the terminal:\n", "\n", - "* Then you must run `ollama serve`\n", "* All of your local models are automatically served on `localhost:11434`\n", - "* Then, select as shown above\n", + "* Run `ollama run <name-of-model>` to start interacting via the command line directly\n", "\n", + "### via an API\n", "\n", - "## Usage\n", + "Send an `application/json` request to the API endpoint of Ollama to interact.\n", "\n", - "You can see a full list of supported parameters on the [API reference page](https://api.python.langchain.com/en/latest/llms/langchain.llms.ollama.Ollama.html).\n", + "```bash\n", + "curl http://localhost:11434/api/generate -d '{\n", + " \"model\": \"llama2\",\n", + " \"prompt\":\"Why is the sky blue?\"\n", + "}'\n", + "```\n", "\n", - "If you are using a LLaMA `chat` model (e.g., `ollama pull llama2:7b-chat`) then you can use the `ChatOllama` interface.\n", + "See the Ollama [API documentation](https://github.com/jmorganca/ollama/blob/main/docs/api.md) for all endpoints.\n", "\n", - "This includes [special tokens](https://huggingface.co/blog/llama2#how-to-prompt-llama-2) for system message and user input." + "#### via LangChain\n", + "\n", + "See a typical basic example of using Ollama via the `ChatOllama` chat model in your LangChain application." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Sure, here's a fun space-themed joke for you:\n", + "\n", + "Why don't astronauts like broccoli? \n", + "Because it has too many \"crisps\" in it!\n", + "\n" + ] + } + ], "source": [ - "from langchain.callbacks.manager import CallbackManager\n", - "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler\n", + "# LangChain supports many other chat models. Here, we're using Ollama\n", "from langchain_community.chat_models import ChatOllama\n", - "\n", - "chat_model = ChatOllama(\n", - " model=\"llama2:7b-chat\",\n", - ")" + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "# supports many more optional parameters. Hover on your `ChatOllama(...)`\n", + "# class to view the latest available supported parameters\n", + "llm = ChatOllama(model=\"llama2\")\n", + "prompt = ChatPromptTemplate.from_template(\"Tell me a short joke about {topic}\")\n", + "\n", + "# using LangChain Expressive Language chain syntax\n", + "# learn more about the LCEL on\n", + "# https://python.langchain.com/docs/expression_language/why\n", + "chain = prompt | llm | StrOutputParser()\n", + "\n", + "# for brevity, response is printed in terminal\n", + "# You can use LangServe to deploy your application for\n", + "# production\n", + "print(chain.invoke({\"topic\": \"Space travel\"}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Optionally, pass `StreamingStdOutCallbackHandler` to stream tokens:\n", + "LCEL chains, out of the box, provide extra functionalities, such as streaming of responses, and async support" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Sure\n", + ",\n", + " here\n", + "'s\n", + " a\n", + " joke\n", + ":\n", + " Why\n", + " did\n", + " the\n", + " astronaut\n", + " break\n", + " up\n", + " with\n", + " his\n", + " girlfriend\n", + "?\n", + " Because\n", + " he\n", + " needed\n", + " more\n", + " space\n", + " to\n", + " explore\n", + ".\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "topic = {\"topic\": \"Space travel\"}\n", "\n", - "```\n", - "chat_model = ChatOllama(\n", - " model=\"llama2:7b-chat\",\n", - " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),\n", - ")\n", - "```" + "for chunks in chain.stream(topic):\n", + " print(chunks)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For streaming async support, here's an example - all possible via the single chain created above." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 13, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "AIMessage(content='\\nArtificial intelligence (AI) has a rich and diverse history that spans several decades. Here is a brief overview of the major milestones and events in the development of AI:\\n\\n1. 1950s: The Dartmouth Conference: The field of AI was officially launched at a conference held at Dartmouth College in 1956. Attendees included computer scientists, mathematicians, and cognitive scientists who were interested in exploring the possibilities of creating machines that could simulate human intelligence.\\n2. 1951: The Turing Test: British mathematician Alan Turing proposed a test to measure a machine\\'s ability to exhibit intelligent behavior equivalent to, or indistinguishable from, that of a human. The Turing Test has since become a benchmark for measuring the success of AI systems.\\n3. 1956: The First AI Program: Computer scientist John McCarthy created the first AI program, called the Logical Theorist, which was designed to reason and solve problems using logical deduction.\\n4. 1960s: Rule-Based Expert Systems: The development of rule-based expert systems, which used a set of rules to reason and make decisions, marked a significant milestone in the history of AI. These systems were widely used in industries such as banking, healthcare, and transportation.\\n5. 1970s: Machine Learning: Machine learning, which enables machines to learn from data without being explicitly programmed, emerged as a major area of research in AI. This led to the development of algorithms such as decision trees and neural networks.\\n6. 1980s: Expert Systems: The development of expert systems, which were designed to mimic the decision-making abilities of human experts, reached its peak in the 1980s. These systems were widely used in industries such as banking and healthcare.\\n7. 1990s: AI Winter: Despite the progress that had been made in AI research, the field experienced a decline in funding and interest in the 1990s, which became known as the \"AI winter.\"\\n8. 2000s: Machine Learning Resurgence: The resurgence of machine learning, driven by advances in computational power and data storage, led to a new wave of AI research and applications.\\n9. 2010s: Deep Learning: The development of deep learning algorithms, which are capable of learning complex patterns in large datasets, marked a significant breakthrough in AI research. These algorithms have been used in applications such as image and speech recognition, natural language processing, and autonomous vehicles.\\n10. Present Day: AI is now being applied to a wide range of industries and domains, including healthcare, finance, transportation, and education. The field is continuing to evolve, with new technologies and applications emerging all the time.\\n\\nOverall, the history of AI reflects a long-standing interest in creating machines that can simulate human intelligence. While the field has experienced periods of progress and setbacks, it continues to evolve and expand into new areas of research and application.')" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + " Sure\n", + ",\n", + " here\n", + "'s\n", + " a\n", + " little\n", + " one\n", + ":\n", + " Why\n", + " did\n", + " the\n", + " rocket\n", + " scientist\n", + " break\n", + " up\n", + " with\n", + " her\n", + " partner\n", + "?\n", + " Because\n", + " he\n", + " couldn\n", + "'t\n", + " handle\n", + " all\n", + " her\n", + " \"\n", + "space\n", + "y\n", + "\"\n", + " jokes\n", + ".\n", + "\n", + "\n", + "\n" + ] } ], "source": [ - "from langchain.schema import HumanMessage\n", + "topic = {\"topic\": \"Space travel\"}\n", "\n", - "messages = [HumanMessage(content=\"Tell me about the history of AI\")]\n", - "chat_model(messages)" + "async for chunks in chain.astream(topic):\n", + " print(chunks)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Take a look at the [LangChain Expressive Language (LCEL) Interface](https://python.langchain.com/docs/expression_language/interface) for the other available interfaces for use when a chain is created.\n", + "\n", + "## Building from source\n", + "\n", + "For up to date instructions on building from source, check the Ollama documentation on [Building from Source](https://github.com/jmorganca/ollama?tab=readme-ov-file#building)" ] }, { @@ -122,42 +248,32 @@ "source": [ "## Extraction\n", " \n", - "Update your version of Ollama and supply the [`format`](https://github.com/jmorganca/ollama/blob/main/docs/api.md#json-mode) flag.\n", - "\n", - "We can enforce the model to produce JSON.\n", + "Use the latest version of Ollama and supply the [`format`](https://github.com/jmorganca/ollama/blob/main/docs/api.md#json-mode) flag. The `format` flag will force the model to produce the response in JSON.\n", "\n", - "**Note:** You can also try out the experimental [OllamaFunctions](https://python.langchain.com/docs/integrations/chat/ollama_functions) wrapper for convenience." + "> **Note:** You can also try out the experimental [OllamaFunctions](https://python.langchain.com/docs/integrations/chat/ollama_functions) wrapper for convenience." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "from langchain.callbacks.manager import CallbackManager\n", - "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler\n", "from langchain_community.chat_models import ChatOllama\n", "\n", - "chat_model = ChatOllama(\n", - " model=\"llama2\",\n", - " format=\"json\",\n", - " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),\n", - ")" + "llm = ChatOllama(model=\"llama2\", format=\"json\", temperature=0)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{\"morning\": {\"sky\": \"pink\", \"sun\": \"rise\"}, \"daytime\": {\"sky\": \"blue\", \"sun\": \"high\"}, \"afternoon\": {\"sky\": \"gray\", \"sun\": \"peak\"}, \"evening\": {\"sky\": \"orange\", \"sun\": \"set\"}}\n", - " \t\n", - "\n" + "content='{\\n\"morning\": {\\n\"color\": \"light blue\"\\n},\\n\"noon\": {\\n\"color\": \"blue\"\\n},\\n\"afternoon\": {\\n\"color\": \"grayish-blue\"\\n},\\n\"evening\": {\\n\"color\": \"pinkish-orange\"\\n}\\n}'\n" ] } ], @@ -170,37 +286,27 @@ " )\n", "]\n", "\n", - "chat_model_response = chat_model(messages)" + "chat_model_response = llm.invoke(messages)\n", + "print(chat_model_response)" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{\n", - " \"name\": \"John\",\n", - " \"age\": 35,\n", - " \"fav_food\": \"pizza\"\n", - "}\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "{\n", + "\"name\": \"John\",\n", + "\"age\": 35,\n", + "\"interests\": [\n", + "\"pizza\"\n", + "]\n", + "}\n" ] } ], @@ -208,6 +314,9 @@ "import json\n", "\n", "from langchain.schema import HumanMessage\n", + "from langchain_community.chat_models import ChatOllama\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", "\n", "json_schema = {\n", " \"title\": \"Person\",\n", @@ -225,17 +334,24 @@ " \"required\": [\"name\", \"age\"],\n", "}\n", "\n", + "llm = ChatOllama(model=\"llama2\")\n", + "\n", "messages = [\n", " HumanMessage(\n", " content=\"Please tell me about a person using the following JSON schema:\"\n", " ),\n", - " HumanMessage(content=json.dumps(json_schema, indent=2)),\n", + " HumanMessage(content=\"{dumps}\"),\n", " HumanMessage(\n", " content=\"Now, considering the schema, tell me about a person named John who is 35 years old and loves pizza.\"\n", " ),\n", "]\n", "\n", - "chat_model_response = chat_model(messages)" + "prompt = ChatPromptTemplate.from_messages(messages)\n", + "dumps = json.dumps(json_schema, indent=2)\n", + "\n", + "chain = prompt | llm | StrOutputParser()\n", + "\n", + "print(chain.invoke({\"dumps\": dumps}))" ] }, { @@ -246,25 +362,32 @@ "\n", "Ollama has support for multi-modal LLMs, such as [bakllava](https://ollama.ai/library/bakllava) and [llava](https://ollama.ai/library/llava).\n", "\n", - "Browse the full set of versions for models with `tags`, such as [here](https://ollama.ai/library/llava/tags).\n", + "Browse the full set of versions for models with `tags`, such as [Llava](https://ollama.ai/library/llava/tags).\n", "\n", - "Download the desired LLM:\n", - "```\n", - "ollama pull bakllava\n", - "```\n", + "Download the desired LLM via `ollama pull bakllava`\n", + "\n", + "Be sure to update Ollama so that you have the most recent version to support multi-modal.\n", "\n", - "Be sure to update Ollama so that you have the most recent version to support multi-modal." + "Check out the typical example of how to use ChatOllama multi-modal support below:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ - "%pip install --upgrade --quiet pillow" + "pip install --upgrade --quiet pillow" ] }, { @@ -275,7 +398,7 @@ { "data": { "text/html": [ - "<img src=\"data:image/jpeg;base64,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4YsvCGhR6Rp8txLbxuzhrhlZ8scnkAD9Kdtbhc2aKWiqEJRRRQAUUUUAJiilooGJS0lFABRRRTAKKKKAEopaKAEooooGFJS0UwEopaOKAEooooAKKKKAEopaKBjcUUtFACUUUUwCiiigBKKWigYlFFFACUtFFAxKKKWmA2loooASiiloAbRS0YoGJRRRTAKSlooGJRS4pKACkpaKBiUUtJTA1aKKK5TnE60UtFACUUUUCCiiigApKWigBKKKKACkpaKAEopaKAEooopgcdrfwu8H+ILt7u+0eMXLnLywO0RY+pCkAn3Iq74d8BeGfCrmXSNKignIwZmZpJMem5iSPwxXSUUrDEopaSmIKWkooAKKKKAEoozRTAKKMUUAFFFFABik6UtFACUUYoxQAUUUUAFFFFABikpaKAEooxRTAKKKKACiiigAooooAKKKKAORi+G/hqHxUfEiW0w1IzGff57bd5zk7eneutxS0UAJRS0lACUUtFMBKKKKACiiigYUUUUAFJS0UAJRR0ooAKKKKACiiigBKKWigYlFFFABRRRimAUYoooASiloxQAlFFFAxKKWjFMBKKKKACkxS0UAFFFFAxKKWkxQAlFLRTASiiigAooooASilpKBhRRRQAUUUUDExRRmimAUUUlABSU6jFADaKXFJQMKKOtFMAxSUtFAxKKKKANSiiiuUwEpaKSmAUtJS0AJRRiigQUUUUAFJS0UAJRRRQAUUUUAFJS0UAJRRRTAKKKKAEopaSgAooooASiiimAUUUUAFFFFABRRRQAUUUUAJRS0UAJRQaKACiiigApKWigBKKWg0wEooooAKKKKACiiigAooooAKKKKACkpaKAEpMU7FJQAlFLSYpgFFFFAwooooAKKKKACkpaKAEoopcUAJRRRTAKTFLRSASilpMUDFpKKWmAlFLSUAFJS0UAJRS0lABRRRTGJRS0mKACiiigAooooAKSlozQMSkp1JQAlFLSUwCiiigApKWigYlFLSUAFJS0UAFJS0UxiUUtJQAlGKWigBKSnUlAxKKWkoAKSlopgalJRRXKYhRRRQAlFLSUwFzRSUUAFFLRigQlFFFABSUtFACUUtJQAUUUUAFJS0UAJRRRQAUUUUwCkpaKAEooooASiiimAUtVrq+trJczyhT2XqT+Fc3qPiZxG5iK20KjmRyM4+vQVcKUp7Fxp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FABRRRQAUUUUAFFFFABRRRQIKSlooGJjNRtEpqSigBqoF7U6iigQUUUUAFFFFMAooooASilpKACiiigBKKKKYwooooJCiiigAooooAKKKKACiiigBKKWkoAKKKKACiiigAooooAKKKKACkpaKBCUUUUAJRS0lMAooooAKKKKACjrRRQAxolalWMLTqKACiiigAooooAKKKKACiiigAooooAKSlooASiiigAooooAKKKKACiiigBKKWjFMBKKKKACiiigAooooAKKKKACiiigAooooGFFFFABSUtFAFuiiisSwooooASilpKACiiimAUUUUAFFFFABRRRQAlFLSUCCiiigApKWigBKKKKYBRRRQAUUUUAFFFFABRRRQAUUUUCCkpaKBiUUUUAFFFFAgooooAKKKKYBRRRQAlFLSUAFFFFABSUtFACUUUUwCiiigQUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUUUUAFFFFABSUtFAhKKKKACkpaKAEopaSmAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUUUUAFFFFACUtFFACUUUUwCiiigAooooAKKKKACiiigYUUUUAW6KKKxNAooooEFFFFACUUtJQAUUUUwCiiigAooooAKKKKAEopaSgQUUUUAFJS0UAJRRRTAKKKKACiiigAooooAKKKKACiiigQUlLRQMSiiigAooooEFFFFABRRRTAKKKKAEopaSgAooooAKSlooASilpKACiiimIKKKKACiiigAooooASilpKACiiigAooooAKKKKACiiigApKWigQlFFFABRRRQAlFLRQAlFFFMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACjFFFACUUtJQAUUUUAFHFcZcePne61C10rQb2+k0+Ro7g71jUEMVG3qWyRwMZPpVK58ZeKjaGdPDUOnIFU79QnOGLMFVRwuCSe+MdTgVapy/porkkegUVyPgLxXdeKNPu5b6KCO4gmC7YVKjaRxkEk5yGrrs0pQcZOLE4tOzDFJS0UhCZopaTpQAUUUUAFFFFABRRRQAUUUUAW6KKKxNQooooAKKKKBBRRRQAlFLSUAFFFFMAooooAKKKKACiiigBKKWkoEFFFFABSUtFACUUUUwCiiigAooooAKKKKACiiigAooooEFJS0UDEooooAKKKKBBRRRQAUUUUwCiiigBKKWkoAKKKKACiiigBKKWkoAKKKKYgooooAKKKKACiiigBKKWkoAKKKKACiiigAooooAKKKKACkpaKBCUUUUAFFFFABRiiigBKKWkxQAUUUUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooASilooA8gaaWy+Jms6CuoC0s9VkAlcxhjuZQ4Ck/dY7ioPPUcZxjrPEkIbw1rsd/qct7DLMoEcHlxNbDcu1CWIB5xnPJB981jax4Wi1f4rMZLhI/9FjvRGQcyFTsC/T5RnHOOnXIo6l4t0/Sru/vP7Bhn1Q3kiiSWMN9mZI0U/Ptyw8zdjBHHpmuqSUnHk3sjZ6tWHfB+RYbnV7Vn+ZxG8eAcOELBiD7bk/MVaji1y/j8Satb+Ir22fTr+6SGEnfFtj+YAg8Y5x7ehqh4C8Q6prPjgTalP57NYtChQDamCrE4XgE7efc1003gC4kn1Bf+EivYbC/uZLie1gQLu3nkbsntgdOfSirK1RuVru3mOTtJ3Of17xnqV/oukCxvI7C7No1/dyFwgOzKqq567mB+X/dqxrl1e6kND1fT9dv7aDWLmGBreKT5IMgK2MdSCD+Oa6jTvBGlWdxeSTww3sc4iSOK4hV1hSNdqqM5ycdT3qCDwLBbQWdvHfS/Z7PU/t8EZXOwdfLBz0zzn3NZ88FsieaPQ5jxH4j1HRdYgtrbVWlt9FSI3YmlCyXjuwymP4vk56cc1saobzXvG1vYWOuXdnZPpK3iNavgOTIQD+II/Kte08FaWlpexahFFqEt3PLPJPNCu5S/Xaf4cY7d6zF8AXcDWUlp4iubee2tPsnmrCCWj3s4HXjGQP8AgIoUofMOaJiS+JNftGn8OHUVkvl1KCzTUfKBIjkB5x03DaOuep5yM1fvLvV/BWrW8cmrTarZ3VpdS+XdD5keGPfkN1weBjpya108AaaNFksHubt7iS4F218ZP33nDo4P4n8z35p2n+C1j1D7fq+p3OrXCxNDGJwFREYYb5R1JBIz703KH9LcOaJzun3d5HcaFf6v4pvBeanJG8dpBFmAqzDCEDgZBxntz1xmvTK4q3+HwtrqyVNavG0yyuluobKQBgrqcjDemSeMdz35rtqio037pM2nsV7p5I4t6PHGByzOCcD2HeqrXV0n2ZpFRfMKgpsPU9ct0HsKuT20dyqrKGIVtw2uV5/A0z7DBvVyHJGDzIxzjpnnn8azILFFFFMC3RSmkrE1CiiigAooooAKKKKBBRRRQAlFLSUAFFFFMAooooAKKKKACiiigBKKDRQIKKKKACkpaKAEooopgFFFFABRRRQAUUUUAFFFFABRRRQIKSloNAxKKKKACiiigQUUUUAFFFFMAooooASilpKACiiigAooooASig0UAFFFFAgooopgFFFFABRRRQAlFFFABRRRQAUUUUAFFFFABRRRQAUYoooEJRS0lABRRRQAUUUUAFJS0UAJRS0lMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAIp4VuIWicuFYYJjdkb8GUgj8DVKDQNJtSrR6fbmRc4ldA8hyckl2yxOSTknvWlRQAlFLSUAFFFFABRRRQAUUUUAFFFFACGilpKYBRScKowopkFtHaQrDEoVE+UAADpx0HA6DgcADBcD/2Q==\" />" + "<img src=\"data:image/jpeg;base64,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S43yEdOnQDJwPfvXJeMfgfpHiTU5dT069fS7qdi8yiLzI5GPJYDI2knrg49qHF2C559qnwo0ix1Gzs9Q+IVp9ru38uJGti5z2yRIdozxk4GTVT442Z07xPolk0nmtb6JBEZMY3FXkGce+K7bQf2e7CzvkuNa1dr6JGB+zQw+WrezNknHsMfWt74g/CNfHWu22pLrX2BYLVbYRC08zIDO2c7xj72MY7UWdguYPxo/5JB4e/6+rb/0nkrd+BP/ACTSL/r6m/mK2PGngD/hL/B+n6B/af2T7HLHJ5/keZv2RsmNu4Yzuz1NX/AnhH/hCvDSaP8Abvtu2V5PN8ry/vdsZP8AOmlqFzpaSloqxCUUtJQAUUUUAFFFFABRRRQAUlLRQAlGKKKYBSUtFACUUUUAFFFFAwpKWjmgBKKWkpgJRS0lABRRRQMSilopgJRRRQAUYoooASilpKACkpaKYCUUuKSgYlFLRQAlFFFABikpaKAEoxS4pKAEopaSmMKSiloASilpKYBRiiigBKKWkoGa9FFFchiFFFFACUUtJQMKKKKYhKKWkoAKKKKACiiigAooooAKjmijnheGaNZIpFKujDIYEYII7jFSUUAeSat+z74Xvrtp7K7vtPVjkwxsroPpuGR+ZrofB3wo8OeDLoXtok93fgELcXLAlAeDtAAA474z713VJSsMKSloqhCUtJS5oAKSiigAooooEJRS0UDG0UtJTAKKKKBBRRRQAUUYooGJRS0YoASiijFAgooooGFFFFABRiiigApKWigBKKXFJQB53pXwh0rSvGo8Txajevci4kuPJYLsy+7I6Zx81eiYoootYBKKWkpgFFFFABRRRQAUUUUAFFFFAxKKWjFADaKWimAlFFFABRRRQAUUUUAFFFFACUUUuKAEooopgJRS0YoASiiigYUUUUAFJS0UAJRiiimAlFLSUAFGKKKBiUUtGKYCUUUUAFJS0UAJRRiigApKWimAlFFFAxKKWigBKKKKAEIoxS0UDEpKWimIbS0tJigYYpKWjFACUUUUwNaiiiuQzCiiigQUUUUAJRS0lAwooopiEopaKAEooooAKKKKACiiigAooooASilpKACjFFFMQlFLRQMSilFJQAUUUUCCkpaKBjaKWkpiCiiigAooooAKKKKBhRRRQAlLRRQAUlBooAKKKKACiiigAo60UUAJRS0U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UUUAFFFFABijFFFMBKKWkxQAUUUUAFFFFACZpaKKBluiiisSwooopiCiiigAooooAKKKKACiiigAooooASilpKACiiigAooooAKSlpKACiiimIKKKKACiiigAooooASilpKAEwDTTED6U+igBoQCnUUUAFFFFABRRRQAUUUUAFJilooEJRS0lABRRRTAKKKKAEopaSgYUUUUAFFFFABRRRQIKKKKACilpKACkpaKAEooopgFFFFABRRRQAUUUUCCkpaKAEopTSUAFFFFABSEZpaKAIzED6ULGBUlJQAUUUUAFFFFABRRRQAUUUUAFFFFABSUtFMBKKXFJQAUUUUAFFFFIAooopgFFFFABRRRQAlFLRQAlFFFABRRRQAUUUUAJRS0UwEooooAKKKKALlJS0ViaCUUUUAFFFFMQUUUUAFFFFABRRRQAUUUUAFFFFACUUtJQAUUUUAFFFFABSUtJQAUUUUxBRRRQAUUUUAFFFFACUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRiiigQlFLRQMSiiimAUUUUCEopaSgAooooGFFFFAgooooAKKKKACiiigApKWigBKKKKACiiimAUUUUAFFFFAgooooASilpKACiiigAooooASilpKACiiigAooooAKKKKACiiigAooo70AFFFFACUUtFMBKKKKACiiigAooopAFFFFMAooooAKSlooASilooASiiigAooooAKKKKACkpaSmBcooorE0CkpaKAEooooAKKKKYgooooAKKKKACiiigAooooAKKKKAEopaSgAooooAKKKKACkpaSgAooopiCiiigAooooAKKKKAEopaSgAooooAKKKKACiiigAooooAKKKKACiiigQUlLRQMSiiimAUUUUhCUUtJTAKKKKACiiigAooooAKKKKACiiigAooooASilpKACiiimAUUUUAFFFFAgooooASilpKACiiigAooooASilpKACiiigAooooAKKKKACiiigAooooAKKKKACiiimAlFLRQAlFFFABRXP33jPRNPvprGW4le6hBLxRQO5UYyegx0rMj+JGnXTbbLTdUujgkGOBQCB7lq0VGbV0iuVnZ0VzfhTxfB4qF0YbWS3NuwBEhBJz/LpXSVEouL5Zbiaa0YUUUUhBRRRQAUUUUAJRS4pKACiiigAooooAt0UUVkaBRRRQAUlLRQAlFFFABRRRTEFFFFABRRRQAUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUlLSUAFFFFMQUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUCCkpaKBiUUUUwCiiigQlFLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAHFJR3paAEopaSgAooopgFFJS0CCiiigBKKWkoAKKKKACiiigBKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkxS0UwPKNXvp9I+KN7bxSW8K6nFHGZZl4TKAAg/UV1usx3B0XVoLu7SW28lUijtod0q8DJIzye/GMVheL/AAzHrvj/AE1ZnaKB7U7nCn5irE7c9uDVLWtctbDULky6Kl7epKI03glFCrjOe4yM4+td1lNR5d7G29rEXwpla217WbOeTfK2DuzneVYgnPfrWxDf+J9SudeuLPVreKPTruWKO3ktwQ4XkAtnI44rG8Gaprup+K7STUI2MKRNGvlx7UUEEknHAOQP0rqZvA0j3OoGLXLyC1v5nlngjRRuLdRu+nFOs0qjcrar1CVr6mRqvjvUJtD0uXTPIivZoGurjzPuqiZGBn+8wwO/bvU2ra3rVx/Y1/pWsRw2erTRwJEbZWMJI5JOeeQeOK3bHwTpNrPcNLCt1FIkcUcU6BliRBgAZ9+Se5qKDwTa20VpBHdyiC01A30EeBhP+mY9s81l7SkvhQrxMTWvF2paNq0FuLmCe305Yv7TkKBWmZzjCrnqF+bAP8q0tSv9Z1Dxkuk6Vqsdnb/2eLsOYBKGO/b6+hFX4fBelG0u4b6Jb2S5mkmeaVBuDPwdp/hwAMYqivgWaGW1ntdeureeC0Fn5ixqS0YYsBz+A/Clz0um4XiZQ8X64yrow+zDVzqBs/tO0+XtAzvx6+1T3fiTWvCuoS2eq3EGpI9pJcQSrF5TblGdpAOMe9av/CCacNJFotxci5Fx9rF7v/e+d/ez0/D+vNOtfBdv9pnutVvrjU7iWA2+6YBQqHqAB0PvVc9HtoF4mZpes6qNU0oapr9qsl+izLYR2hxsYZAD54P1/Wu7rkrLwP8AZNQsJn1i7uLawbdbW8gU7OMAbupFdbWNVxbXKTK3QguZJY4w0SKxzyXbaqjGSSapnU5Ato7QKqz7AAZPmJJwcDHIA55xxVu8tFvIfKaR0XcGOzHzY7HIIIqN7ASFPMuZ3C7SykrhipyCQBxz6YzWRBbooooAt0UUVkaBRRRQAUUUUAFJS0GgBKKKKACiiimIKKKKACiiigAooooAKKKKACiiigBKKWkoAKKKKACiiigApKWkoAKKKKYgooooAKKKKACiiigBKKKKACiiigAooooAKKKKACiiigAooooAKKKKBBSUtJQMKKKKYBRRRQISilpDQAUUUUAFFFFABRRRQAUUUUAFFFFABSUtFACUopKKACiiigAoxRRTAKKKKACiiigQlFBooAKKKKACiiigBKKU0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA10WRCjDKkYIqC3sLS1ULDbxoBnGBz6nmrNFO7HcTFFLQelAhKKKKACiiigAooooAKKKKACiiigAoooNAH//2Q==\" />" ], "text/plain": [ "<IPython.core.display.HTML object>" @@ -319,7 +442,7 @@ " display(HTML(image_html))\n", "\n", "\n", - "file_path = \"/Users/rlm/Desktop/Eval_Sets/multi_modal_presentations/DDOG/img_23.jpg\"\n", + "file_path = \"../../../static/img/ollama_example_img.jpg\"\n", "pil_image = Image.open(file_path)\n", "\n", "image_b64 = convert_to_base64(pil_image)\n", @@ -328,40 +451,52 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "AIMessage(content='90%')" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "90%\n" + ] } ], "source": [ + "from langchain.schema import HumanMessage\n", "from langchain_community.chat_models import ChatOllama\n", - "from langchain_core.messages import HumanMessage\n", "\n", - "chat_model = ChatOllama(\n", - " model=\"bakllava\",\n", - ")\n", + "llm = ChatOllama(model=\"bakllava\", temperature=0)\n", "\n", - "# Call the chat model with both messages and images\n", - "content_parts = []\n", - "image_part = {\n", - " \"type\": \"image_url\",\n", - " \"image_url\": f\"data:image/jpeg;base64,{image_b64}\",\n", - "}\n", - "text_part = {\"type\": \"text\", \"text\": \"What is the Daollar-based gross retention rate?\"}\n", "\n", - "content_parts.append(image_part)\n", - "content_parts.append(text_part)\n", - "prompt = [HumanMessage(content=content_parts)]\n", - "chat_model(prompt)" + "def prompt_func(data):\n", + " text = data[\"text\"]\n", + " image = data[\"image\"]\n", + "\n", + " image_part = {\n", + " \"type\": \"image_url\",\n", + " \"image_url\": f\"data:image/jpeg;base64,{image}\",\n", + " }\n", + "\n", + " content_parts = []\n", + "\n", + " text_part = {\"type\": \"text\", \"text\": text}\n", + "\n", + " content_parts.append(image_part)\n", + " content_parts.append(text_part)\n", + "\n", + " return [HumanMessage(content=content_parts)]\n", + "\n", + "\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "\n", + "chain = prompt_func | llm | StrOutputParser()\n", + "\n", + "query_chain = chain.invoke(\n", + " {\"text\": \"What is the Dollar-based gross retention rate?\", \"image\": image_b64}\n", + ")\n", + "\n", + "print(query_chain)" ] } ], @@ -381,7 +516,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.9.1" } }, "nbformat": 4, diff --git a/docs/docs/integrations/llms/ollama.ipynb b/docs/docs/integrations/llms/ollama.ipynb index 9637c965ccd6f..79ea75898789d 100644 --- a/docs/docs/integrations/llms/ollama.ipynb +++ b/docs/docs/integrations/llms/ollama.ipynb @@ -18,84 +18,164 @@ "\n", "First, follow [these instructions](https://github.com/jmorganca/ollama) to set up and run a local Ollama instance:\n", "\n", - "* [Download](https://ollama.ai/download)\n", - "* Fetch a model via `ollama pull <model family>`\n", - "* e.g., for `Llama-7b`: `ollama pull llama2` (see full list [here](https://ollama.ai/library)\n", - "* This will download the most basic version of the model typically (e.g., smallest # parameters)\n", - "* On Mac, it will download to \n", + "* [Download](https://ollama.ai/download) and install Ollama onto the available supported platforms (including Windows Subsystem for Linux)\n", + "* Fetch available LLM model via `ollama pull <name-of-model>`\n", + " * View a list of available models via the [model library](https://ollama.ai/library)\n", + " * e.g., for `Llama-7b`: `ollama pull llama2`\n", + "* This will download the default tagged version of the model. Typically, the default points to the latest, smallest sized-parameter model.\n", "\n", - "`~/.ollama/models/manifests/registry.ollama.ai/library/<model family>/latest`\n", + "> On Mac, the models will be download to `~/.ollama/models`\n", + "> \n", + "> On Linux (or WSL), the models will be stored at `/usr/share/ollama/.ollama/models`\n", "\n", - "* And we specify a particular version, e.g., for `ollama pull vicuna:13b-v1.5-16k-q4_0`\n", - "* The file is here with the model version in place of `latest`\n", + "* Specify the exact version of the model of interest as such `ollama pull vicuna:13b-v1.5-16k-q4_0` (View the [various tags for the `Vicuna`](https://ollama.ai/library/vicuna/tags) model in this instance)\n", + "* To view all pulled models, use `ollama list`\n", + "* To chat directly with a model from the command line, use `ollama run <name-of-model>`\n", + "* View the [Ollama documentation](https://github.com/jmorganca/ollama) for more commands. Run `ollama help` in the terminal to see available commands too.\n", "\n", - "`~/.ollama/models/manifests/registry.ollama.ai/library/vicuna/13b-v1.5-16k-q4_0`\n", + "## Usage\n", "\n", - "You can easily access models in a few ways:\n", + "You can see a full list of supported parameters on the [API reference page](https://api.python.langchain.com/en/latest/llms/langchain.llms.ollama.Ollama.html).\n", "\n", - "1/ if the app is running:\n", + "If you are using a LLaMA `chat` model (e.g., `ollama pull llama2:7b-chat`) then you can use the `ChatOllama` interface.\n", "\n", - "* All of your local models are automatically served on `localhost:11434`\n", - "* Select your model when setting `llm = Ollama(..., model=\"<model family>:<version>\")`\n", - "* If you set `llm = Ollama(..., model=\"<model family\")` withoout a version it will simply look for `latest`\n", + "This includes [special tokens](https://huggingface.co/blog/llama2#how-to-prompt-llama-2) for system message and user input.\n", + "\n", + "## Interacting with Models \n", "\n", - "2/ if building from source or just running the binary: \n", + "Here are a few ways to interact with pulled local models\n", + "\n", + "#### directly in the terminal:\n", "\n", - "* Then you must run `ollama serve`\n", "* All of your local models are automatically served on `localhost:11434`\n", - "* Then, select as shown above\n", + "* Run `ollama run <name-of-model>` to start interacting via the command line directly\n", "\n", + "### via an API\n", "\n", - "## Usage\n", + "Send an `application/json` request to the API endpoint of Ollama to interact.\n", "\n", - "You can see a full list of supported parameters on the [API reference page](https://api.python.langchain.com/en/latest/llms/langchain.llms.ollama.Ollama.html)." + "```bash\n", + "curl http://localhost:11434/api/generate -d '{\n", + " \"model\": \"llama2\",\n", + " \"prompt\":\"Why is the sky blue?\"\n", + "}'\n", + "```\n", + "\n", + "See the Ollama [API documentation](https://github.com/jmorganca/ollama/blob/main/docs/api.md) for all endpoints.\n", + "\n", + "#### via LangChain\n", + "\n", + "See a typical basic example of using Ollama chat model in your LangChain application." ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "\"Sure! Here's a quick one:\\n\\nWhy don't scientists trust atoms?\\nBecause they make up everything!\\n\\nI hope that brought a smile to your face!\"" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from langchain.callbacks.manager import CallbackManager\n", - "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler\n", "from langchain_community.llms import Ollama\n", "\n", - "llm = Ollama(model=\"llama2\")" + "llm = Ollama(model=\"llama2\")\n", + "\n", + "llm.invoke(\"Tell me a joke\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Optionally, pass `StreamingStdOutCallbackHandler` to stream tokens:\n", - "\n", - "```\n", - "llm = Ollama(\n", - " model=\"llama2\",\n", - " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),\n", - ")\n", - "```" + "To stream tokens, use the `.stream(...)` method:" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "' Artificial intelligence (AI) has a rich and varied history that spans several decades. październik 1950s and has evolved significantly over time. Here is a brief overview of the major milestones in the history of AI:\\n\\n1. 1950s: The Dartmouth Conference - Considered the birthplace of AI, this conference brought together computer scientists, mathematicians, and cognitive scientists to discuss the possibilities of creating machines that could simulate human intelligence. Attendees included John McCarthy, Marvin Minsky, Nathaniel Rochester, and Claude Shannon.\\n2. 1951: The Turing Test - Alan Turing proposed a test to measure a machine\\'s ability to exhibit intelligent behavior equivalent to, or indistinguishable from, that of a human. The Turing Test has since become a benchmark for measuring the success of AI systems.\\n3. 1956: The First AI Program - John McCarthy created the first AI program, called the Logical Theorist, which was designed to reason and solve problems using logical deduction.\\n4. 1960s: Rule-Based Expert Systems - Researchers developed rule-based expert systems, which used a set of rules to reason and make decisions. These systems were widely used in industries such as banking and healthcare.\\n5. 1970s: Machine Learning -Machine learning, a subfield of AI, emerged as a way for machines to learn from data without being explicitly programmed. This led to the development of algorithms such as decision trees and neural networks.\\n6. 1980s: Expert Systems - The development of expert systems, which were designed to mimic the decision-making abilities of human experts, reached its peak in the 1980s. These systems were widely used in industries such as banking and healthcare.\\n7. 1990s: AI Winter - Despite the progress made in AI research, the field experienced a decline in funding and interest in the 1990s, known as the \"AI winter.\"\\n8. 2000s: AI Resurgence - The resurgence of AI began in the early 2000s with the development of new algorithms and techniques, such as support vector machines and deep learning. This led to a renewed interest in AI research and applications.\\n9. 2010s: Rise of Deep Learning - The development of deep learning algorithms, which are capable of learning and improving on their own by analyzing large amounts of data, has been a major factor in the recent progress made in AI. These algorithms have been used in applications such as image recognition, natural language processing, and autonomous vehicles.\\n10. Present Day: AI Continues to Advance - AI is continuing to advance at a rapid pace, with new techniques and applications emerging all the time. Areas of research include natural language processing, computer vision, robotics, and more.\\n\\nSome notable people who have made significant contributions to the field of AI include:\\n\\n1. Alan Turing - Considered one of the pioneers of AI, Turing proposed the Turing Test and developed the concept of a universal machine.\\n2. John McCarthy - McCarthy is known as the \"father of AI\" for his work in developing the field of AI. He coined the term \"Artificial Intelligence\" and was instrumental in organizing the Dartmouth Conference.\\n3. Marvin Minsky - Minsky was a pioneer in the field of neural networks and co-founder of the MIT AI Laboratory.\\n4. Nathaniel Rochester - Rochester was a computer scientist and cognitive scientist who worked on early AI projects, including the development of the Logical Theorist.\\n5. Claude Shannon - Shannon was a mathematician and electrical engineer who is known for his work on information theory, which has had a significant impact on the field of AI.\\n6. Yann LeCun - LeCun is a computer scientist and the director of AI Research at Facebook. He is also the Silver Professor of Computer Science at New York University, and a professor at the Courant Institute of Mathematical Sciences.\\n7. Geoffrey Hinton - Hinton is a computer scientist and cognitive psychologist who is known for his work on artificial neural networks. He is a pioneer in the field of deep learning and has made significant contributions to the development of convolutional neural networks (CNNs).\\n8. Yoshua Bengio - Bengio is a computer scientist and a pioneer in the field of deep learning. He is known for his work on recurrent neural networks (RNNs) and has made significant contributions to the development of CNNs and RNNs.\\n9. Andrew Ng - Ng is a computer scientist and entrepreneur who has made significant contributions to the field of AI. He is known for his work on deep learning and has worked at Google, where he founded the Google Brain deep learning project, and at Baidu, where he led the company\\'s AI group.\\n10. Demis Hassabis - Hassabis is a computer scientist and entrepreneur who is known for his work on deep learning and artificial intelligence. He is the co-founder of DeepMind, which was acquired by Alphabet in 2014, and has made significant contributions to the field of AI.\\n\\nThese are just a few examples of notable people who have made significant contributions to the field of AI. There are many other researchers and scientists who have also made important advancements in the field.'" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "S\n", + "ure\n", + ",\n", + " here\n", + "'\n", + "s\n", + " one\n", + ":\n", + "\n", + "\n", + "\n", + "\n", + "Why\n", + " don\n", + "'\n", + "t\n", + " scient\n", + "ists\n", + " trust\n", + " atoms\n", + "?\n", + "\n", + "\n", + "B\n", + "ecause\n", + " they\n", + " make\n", + " up\n", + " everything\n", + "!\n", + "\n", + "\n", + "\n", + "\n", + "I\n", + " hope\n", + " you\n", + " found\n", + " that\n", + " am\n", + "using\n", + "!\n", + " Do\n", + " you\n", + " want\n", + " to\n", + " hear\n", + " another\n", + " one\n", + "?\n", + "\n" + ] } ], "source": [ - "llm(\"Tell me about the history of AI\")" + "query = \"Tell me a joke\"\n", + "\n", + "for chunks in llm.stream(query):\n", + " print(chunks)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To learn more about the LangChain Expressive Language and the available methods on an LLM, see the [LCEL Interface](https://python.langchain.com/docs/expression_language/interface)" ] }, { @@ -106,16 +186,14 @@ "\n", "Ollama has support for multi-modal LLMs, such as [bakllava](https://ollama.ai/library/bakllava) and [llava](https://ollama.ai/library/llava).\n", "\n", - "```\n", - "ollama pull bakllava\n", - "```\n", + "`ollama pull bakllava`\n", "\n", "Be sure to update Ollama so that you have the most recent version to support multi-modal." ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -132,7 +210,7 @@ { "data": { "text/html": [ - "<img src=\"data:image/jpeg;base64,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4YsvCGhR6Rp8txLbxuzhrhlZ8scnkAD9Kdtbhc2aKWiqEJRRRQAUUUUAJiilooGJS0lFABRRRTAKKKKAEopaKAEooooGFJS0UwEopaOKAEooooAKKKKAEopaKBjcUUtFACUUUUwCiiigBKKWigYlFFFACUtFFAxKKKWmA2loooASiiloAbRS0YoGJRRRTAKSlooGJRS4pKACkpaKBiUUtJTA1aKKK5TnE60UtFACUUUUCCiiigApKWigBKKKKACkpaKAEopaKAEooopgcdrfwu8H+ILt7u+0eMXLnLywO0RY+pCkAn3Iq74d8BeGfCrmXSNKignIwZmZpJMem5iSPwxXSUUrDEopaSmIKWkooAKKKKAEoozRTAKKMUUAFFFFABik6UtFACUUYoxQAUUUUAFFFFABikpaKAEooxRTAKKKKACiiigAooooAKKKKAORi+G/hqHxUfEiW0w1IzGff57bd5zk7eneutxS0UAJRS0lACUUtFMBKKKKACiiigYUUUUAFJS0UAJRR0ooAKKKKACiiigBKKWigYlFFFABRRRimAUYoooASiloxQAlFFFAxKKWjFMBKKKKACkxS0UAFFFFAxKKWkxQAlFLRTASiiigAooooASilpKBhRRRQAUUUUDExRRmimAUUUlABSU6jFADaKXFJQMKKOtFMAxSUtFAxKKKKANSiiiuUwEpaKSmAUtJS0AJRRiigQUUUUAFJS0UAJRRRQAUUUUAFJS0UAJRRRTAKKKKAEopaSgAooooASiiimAUUUUAFFFFABRRRQAUUUUAJRS0UAJRQaKACiiigApKWigBKKWg0wEooooAKKKKACiiigAooooAKKKKACkpaKAEpMU7FJQAlFLSYpgFFFFAwooooAKKKKACkpaKAEoopcUAJRRRTAKTFLRSASilpMUDFpKKWmAlFLSUAFJS0UAJRS0lABRRRTGJRS0mKACiiigAooooAKSlozQMSkp1JQAlFLSUwCiiigApKWigYlFLSUAFJS0UAFJS0UxiUUtJQAlGKWigBKSnUlAxKKWkoAKSlopgalJRRXKYhRRRQAlFLSUwFzRSUUAFFLRigQlFFFABSUtFACUUtJQAUUUUAFJS0UAJRRRQAUUUUwCkpaKAEooooASiiimAUtVrq+trJczyhT2XqT+Fc3qPiZxG5iK20KjmRyM4+vQVcKUp7FxpylsdJd31tZLunlCnsvUn8KwLrxJO74tkEaA9WGSf8K851fx1bxO62QN1Mesrk7c/zNcXqGrX2pyb7q4Z8HKqOFX6CvWw+Vt6z/r5HVDDpbn0NZeI4ZcJdL5Tf3hyp/wrZR1kQOjBlPQg5Br5z0vxdqGn7Y5W+1QD+GQ/MPo3+Oa73QfGFvcsBZ3RhmPWCTjP4dD+FZYjLZQ1iTPDdYnqOKKxLLxHDLhLpfKb+8OVP+FbKSLIgdGDKehByDXmyhKO6OWUXHcdQaKKkQlFLRQAlFFFABRRRQAUUUUAJRRR2pgFFFFABRRRQAUUUUAFFFFABRRRQAlFLRQAlJS0UAJRS4pKYBRRRQMKKKKACiiigBKKWkoGFFFFAgooqG4u4LRN00gX0Hc/hQlfYdrk1QXN3BaJumkC+g7n8Kwb7xG2xvIxDGBzI5GcfyFcBrHjm1hdltibyfu5PyD8e/4fnXXRwdSo9jaFBvc9Bu/EMjHbapsX+8wyT+FT2fiCN8LdJsP99eR+VeA6lrV/qr5upyUzkRrwo/Crul+KtR03ahf7RAP+Wcpzgex6ivRllfuabnQ6EbWPoqORJUDxurKehByKdXl2h+MbW5dRb3DW1wesUhxu+nY121n4gR8JdLsP99eR+VeZVws6bs0c0qMo7G3RTY5ElQPG4ZT0IOadXOZBSUtFACUUUUAFFFFMYlFLSUAFFFFACYpaKKACkpaKBiUlLiimAlFLikoAKKKKADFJS0UDEooooAKKKKBhSYpaSmAUUuKTFABSUUtADaKWigZpUUUVzmQUUYopCCiiigAxSUtJTAKM0YooELSUUuaAEopaSgAooooASilooASiiigBMUUtFACUUUUAFFFFMAoqtd39tZLmeUKey9Sfwrm9S8UP5bmIrbQqOZHIzj69BV06Up7IuNOUtjpLu+trJczyhT2XqT+Fc3qPid/LcxFbaEdZHIzj69BXnur+O7eJnWyBupj1lcnbn+ZridQ1a+1STfdzs4zwnRV+gr2cNlTes/6+R1Qw6WrO21fx1bxM62QN1Mesrk7c/wAzXFahq19qkm+7nZwDwnRV+gqlRXs0sPTpfCjpUUgooorYoWjpSUtSB0Gl+LtQ0/bHK32qAfwyH5h9G/xzXe6D4wt7lgLS6MMx6wScZ/DofwryKlBxXLWwlOp0sRKCZ9JWXiOGXCXS+U394cqf8K2UdZEDowZT0IOQa+c9L8Xahp+2OVvtUA/hkPzD6N/jmu90Hxhb3LD7HdGGY9YJOM/h0P4V4uIy6UNYnLPDdYnqNFYll4ihlwl0vlN/eHKn/CtlJFkQOjBlPQg5BrzZQlHdHLKLjuOxRiiipEJRS0lABRRRQAYooooAKSlooASiijFMAooooAKKKKACiiigAooooAKKKKAEopaTFACYopaKYCUUUUAFFFFAwooqC5vILRN00gX0Hc/hQk3sC12J8VBc3cFom6eRV9B3P4VgX/iRtjeRiGMDmRyM4/kK4DWPHNrC7i1JvJ+7k/IPx7/h+dddDBVKr0RvCi3ud/f+I22N5GIYwOZHIzj+QrgNY8c2sLuLYm8n7uT8g/Hv+H51xGpa1f6q+bqclM5Ea8KPwqhXt0MthDWR1wpKJoalrV/qr5upyUzkRrwo/Cs+iivRUVFWSNApaSigYtbml+KtR03ahf7RAP8AlnKc4HseorDoqJwjNWkhNXPWND8Y2ty6i3uGtrg9YpDjd9Ox/nXa2fiCN8JdJsP99eR+VfOVbml+KtR0zahf7RAP+Wcpzgex6ivMr5dGWsDKdFSPomOVJUDxuGU9CDmn15fofjG1uXUW9w1tcHrFIcbvp2NdrZ+II3wl0mw/315H5V49XDTpu1jknRlE26KakiSoHjdWU9CpzTq5zISilooASiiigYmKKWimAlFFFABRRRQAUUUUDEopaKAG0UtFMBKKKKACiiigBKKWjFAxKMUUUAJmloopjENFBooAKKMUUAaVFFFcxkFFFFACUUUUxhRRRQIKSlooASilpKBBS0lFAC0lFLQAlFGKKACkpaKAEooooAKMVWu7+2slzPKFPZepP4VzepeKH8tzEVtoVHMjkZx9egrSnRnPZFxpylsdHd39tZLmeUKey9Sfwrm9S8TuI3MRW2hUcyORnH16CvPNX8d28TutkDdTHrK5O3P8zXE6hq19qkm+7uGcA8J0VfoK9nDZTJ6z0/rsdUMOlqzttX8d28TutkDdTHrK5O3P8zXFahq19qkm+7nZwDwnRV+gqjS17lLDU6XwrU6UkgooorYYUtJRSAWiiikMKKKKQC0UlLUgFL0NJRSA6DS/F2oaftSVvtUA/hkPzD6N/jmu90Hxhb3LAWl0YZj1gk4z+HQ/hXkVL9K5a2Ep1OliJQTPpKy8RQy4S6Xym/vDlT/hWyjrIgdGDKehByDXznpfi3UNP2xyt9qgH8Mh+YfRv8c13uheMLe4YfY7owzHrBJxn8Oh/CvFxGXShrE5Z4brE9RorEsvEUMuEul8pv7w5U/4VspIsih0YMp6EHINedKEo7o5ZRcdxaKWipEJRRRQAUUUUAFFFFACUUtFMBKKKKACiiigAooooAKKKKACiiigApKWigBKKWq9zdwWibp5AvoO5+goSb2GtSaoLm7gtE3TSBfQdz+FYN/4kbY3kYhjA5kcjOP5CvPtY8dWsLutqTeT93J+Qfj3/D867aGCqVXojeFBvc9Av/EjbG8jEMYHMjkZx/IV5/rHjq1hd1tSbyfu5PyD8e/4fnXEalrV/qr5upyUzkRrwo/Cs+vdw+WQp6zOqFKMTR1LWr/VXzdTkpnIjXhR+FZ9JS16MYqKskahRRRTAWikpaQwoooqQClpKKQC0UUUgCt3S/FWo6btQv8AaIB/yzlOcfQ9RWFRUThGatJCauesaH4xtbl1FvcG2uD1ikON307Gu1s/EEb4S6TYf768j8q+c63NL8Vajpu1C/2iAf8ALOU5wPY9RXmV8ujLWJlOipH0THKkqB43DKehByKdXl+h+MbW5dRb3DW056xSHG76djXa2fiCN8LdJsP99eR+VePVw06btY5Z0ZRNukpI5ElQPG6sp6EHNOrAxEopaTFABRRRQMSilopgJRRRQAUlLRQAUlLSUDCjFLRigBtFLSYpgFFFFABSYpaKAEopaSgYUUUUAFGKKKBmjRS0lc5kFFFFABRRRQAUlLRQAlFFFMYUUUUCCkopaAEopaSgQUtJRQAUVWu7+2slzPKFPZepP4VzWpeKX8tzEVtoVHMjkZx9egrSnRnPZFxpylsdJd39tZLmeUKey9Sfwrm9S8UOI3MRW2hUcyORnH16CvOtY8eW8TutkDdTHrK5O3P8zXE6jq99qsm+7uGcA8J0VfoK9vC5PKXvT0/rsdUMOlqzt9Y8eW8TutkDdTHrK5O3P8zXEahq99qkm+7uGcA8J0VfoKo0te7RwtOj8K1OhJIWikpa3KCiiikAtFJS1IBRRRSAKWkopALRRRSGFFFFIBaKSlqQCiiikAtAODSUtIDoNL8W6hp+1JW+0wD+GQ/MPo3+Oa7zQvGFvcMBaXRhmPWCTjP4dD+FeR0v0rlq4WnU6ESgmfSFl4ihlwl0vlN/eHKn/CtpHWRA6MGU9CDkGvnTS/FuoaftSVvtUI/hkPzD6N/jmu80Hxhb3LAWd0YZj1gk4z+HQ/hXjV8ulDWJyzw3WJ6jRWHZeIoZcJdL5Tf3hyp/wraSRZEDowZT0IOQa86UJR3Ryyi47imiloqRCUUtJQAUUUUAFFFFACUUUtMBKKKKACiiigAooqvc3cFom6eUJ6DufwoSvsCV9ixUFzdwWibp5FT0Hc/QVz9/4lbY3kYhjA5kcjOP5CvPtZ8d2sLutqWvJ+7k/IPx7/h+dduHwNWs7JHRCg3ueg3/AIlbY3kYhjA5kcjOP5CvPtY8d2sLutqTeT93J+Qfj3/D864bUtbv9WfN1OSmciNeEH4Vn172HyqFPWZ1QpRiaOpa3f6s+bqclM5Ea8IPwqhSUV6kYKKtFWNBaKKKBhRRRSAWikpaQBRRRSAWikpaQwoooqQClpKKQC0UUUgClpKKQC1uaX4p1HTdqF/tEA/5ZynOB7HqKw6KznCMlaSFa56vofjG1uXUQXDW056xSHG76dj/ADrtbPxBG+Euk2H++vI/KvnStvS/FOo6btQv9ogH/LOQ5wPY9RXnV8vjLWJlOipH0THKkqB43DKehBzTq8v0Pxja3LqLe4a2uD1ikON307Gu1s/EEb4S6XYf768j8q8erhp03scs6Mom3SUkciSoHjdWU9CDkU6sDESilxSUAFFFFMYlFLSUAFFFFABRRRQAUlLRQMSkNOpKAEopaSmAUUUUAFJS0UDEopcUlAGlRRRXOZiUUtJQAUUUUAFFFFABRRRQAlFFFMAooooAKTrVe7v7ayTM8oU9lHJP4VzOpeKXEbmJltoVHMjkZx9egrSnRnU2RUacpbHS3d/bWK5nlCnso5J/Cua1LxS4jcxFbaFRzI5GcfXoK861jx7bxM62IN3Mesrk7M/zP6fWuI1HV77VZN93cM4B4Toq/QV7eFyaUveqaf12OqGHS3O31jx7bxM62QN3Mesrk7M/zP8AnmuI1DV77VZN93cM4B4Toq/QVRor6CjhKVH4Vr3OhJIWiiiugoKKKKQC0UlLUgLRSUtIAooopALRSUtSAUUUUgClpKKQC0UUUhhRRRSAWikpakAooopALRSUtIBaOhyKSlpAb+l+LdQ0/bHK32mEfwyH5h9G/wAc13mheMLe4YfZLowzHrBJxn8Oh/CvJKAa5auFp1OhEoKW59IWXiOGXCXS+U394cqf8K2kdZFDIwZT0IOQa+dNL8W6hp+2OVvtMI/hkPzD6N/jmu80Lxhb3DD7JdGGY9YJOM/h0P4V49fL5R1ictTDdYnqNGKxLLxHDLhLpfKb+8OVP+FbKSLIgdGDKehByDXnShKO6OWUXHcWilpMVIgooooAKKKKAEopaguby3tE3TyKnoO5+goV3ogSuTVBc3kFom6eRV9B3P0Fc/qHiVtjeRiGMDmRyM4/kK891nx5awO62pN7cd3J+Qfj3/D867sPgatZ2SN4UG9z0K/8StsbyMQxgcyORnH8hXnus+O7WB3W1LXs/dyfkH49/wAPzrhtT1vUNWfN1OSmciNeEH4f41n19BhsohT1nqdcKUYmjqWt6hqz5upyUzkRrwg/Cs+kor1owjFWirI0FooooGFLSUUgFpaSikAtFFFSAUUUUgFopKWkAUUUUgFopKWkMKKKKkApaSikAtFFFIApaSikAtLSUVIC1t6X4q1HTdqF/tEA/wCWchzgex6isSiolCMlaSE1c9W0Pxja3LqILg2056xSHG76dj/Ou1s/EEb4S6TYf768j8q+dK29L8U6jpu1C/2iAf8ALOQ5wPY9RXnV8vjLWJlOipH0THKkqB43VlPQg5p1eX6H4xtbl1FvcG2nPWKQ43fTsa7Wz8QRvhLpNh/vryPyryKuGnTexyzoyibeKSkjkSVA8bqynoQc06sDESiiigApKWimMSiiigAooooASloooAMUlFLQMTFJS0UAJRS4pKYBRRRQBo0UUVzEBRRRTASilpKACiiigQUlLRQMKKSjNABXNa7rc9pPJbxMsSIuWkPXGM/hXSnpXn3jH/mJf9e7f+gV04SmqlSzNaKTlqcXrHj23iZ1sQbuY9ZXJCZ/mf0+tcRqOr32qyb7u4ZwDwnRV+gqhS19zQwdKj8K17nekkFLSUV0lC0UUUgClpKKQC0UUUgCiiikAtFJS1IC0UlLSAKKKKQC0UlLUgFFFFIApaSikAtFFFIYUUUUgFopKWpAKKKKQC0UlLSAWikpaQBR0PFFFIDf0vxbqGn7Y5W+0wj+GQ/MPo3+Oa77QPFsN23+hXDRzYy0D9/w6GvIq6Dwb/yMUf8A1zf+VceIw8JRcrETgmj3/S71r6zErqFbJBA6VdrG8O/8g/8A4Ga2a+ZqJKTSPMmrSaQUlLRUkiUUtIelAGFrWrT2swt4Nq5XJfqa8y1nx5aQO62pN7cd3J+Qfj3/AA/Ou58Sc3b/APXL/GvnyvosowdKtFymtrHdRiuW5o6nreoas+bqclM5Ea8IPw/xrOoor6WMIwXLFWR0C0UlLTGFFFFIApaSipAWiiikAUtJRSAWlpKKQC0UUVIBRRRSAWikpaQBRRRSAWikpaQwoooqQClpKKQC0UUUgClpKKQC0tJRUgLRRRSAK3NL8U6jpu1C/wBogH/LOQ5wPY9RWHRUSgpK0kDVz1fQfF1veSKtrM8FyesL/wAX07GvQNKvnvrYvIqqyttOO/FeA+Ff+Rls/q3/AKCa9y8Pf8e0n+//AEFeJj6MaexyV4pK5t0UUV5hyCUUtGKAEooooGJRS0UwEooooAKKKKAEpaKKAEopaKBjaKWjFMDQooormICiiigAooopgJRS0lABRRRQIKSlooGIelefeMeupf8AXu3/AKBXoJ6V594x/wCYl/17t/6BXZgf4yNqHxHgtFFFfoK2O8WikpaAClpKKkYtFFFIApaSikAtFFFIAooopAbPhbR49f8AEdnpksrxJOxDOgBIwCe/0o8U6E3hzX7jT95kjXDRSH+NCMg1ofDg/wDFe6Wf9tv/AEBq2dZtJfFWgWdxbr5l/p94dPmA6lGb92T/ACrzKuIlSxSTfu2X3u+v4WM3JqfkZ+l+C0vfA194iuLmSJ4QzQRADEgXAJPtk4rn7mCwTSbKaCa4a8cv56PHiNQDxtPevS7q5jGm+JtItWBtdJ0yO2THRnzl2/E/yrCmtEv/AAj4JtJDhJ7uWNj7GQA1z0sXNtyns5fcuW5MZvqcdpNmt7q1lbyh/JmuEiYrxwWAOD61P4j0+DSvEd/YW2/ybeVkTecnA9TXb3fizU7Xx9Ho1qYodKguktVtBCpTaGAyeM575rJ1+w0e88Ya22p64dOkW7YIgs2m3D1yCMVrDEzdVSmrJxvZXfVdkUpO92Zf9jWv/CBrrI8z7Ub77Pjd8u3bnp65rCVAJlSXco3ANgcgfSvQLTVJtB+Gkk2lzo7jVmjiuWhG4Ls+8obO0kD8M1FrN5NrPhXw1q19tkv2u3gefaAzqGGM4qaeImpO60cmt9dr7WDmdzjtWhsrfUpotOlnltVxsedNjnjnI+tVfKkBI8tsqMnjoK9WvEit/HXivWDEks+nWiy26uMgOVADY9qzdC8S6pr3h/xOmpzrcNHYMySGNQ65PIBAHHt7Uo4yXIpKN0rX111t5efkCm7XOZ1bQ7ez8L6FqFuJWub8S+aCcj5WwMDFc+QQcEYNeiX2v6hoXw+8M/2dIsM0qz5mCAuAH6AkcA9/oKzfHr/a4NA1SRU+1XliHuHVQN7A4ycd6eHrzuoyWjcknfs3/W4Rk9n5nG0UUV3moUUUUgFopKWpAKKKKQC0UlLSAWikpaQBXQeDP+Rij/65v/KufroPBn/IxR/9c3/lWNb+HL0E9j3Pw7/yD/8AgZrZrG8O/wDIP/4Ga2a+Uq/GzyqnxMKKKKzJCg9KKQ9KAOO8Sf8AH4//AFy/xr57r6E8Sf8AH4//AFy/xr57r67Iv4cvkejQ+BC0UlLXuGoUUUUgFopKWkMKKKKQBS0lFSAtFdd/wjGj6Xp9pP4h1WeC4vIhNHbW0O4oh6MxJA59KUeD4IfEWiwfbPtmk6o48m5jGxmXOCCDnDCuP67R/Po9bb2fUjnRyFLW3c6dpltf63byy3KNau6Wqou8OQxGHPYY71iKrNnapOBk4HStoVYzV0UncKWtbw1pkWq+JNOsLoSLDcSBWK8Ej2NVNTtBaapewRK/kwzvGrHngMQMn1qfaw5+Tra4X1sVKK1bHw9e6ho19qcO0Q2ezerA7n3HA24GDVyPRLd/AkmsYlN4t8LYKD8u3bnpjrms5Yimna/W3zYcyOeop2xt+zad2cbcc5pGUqxVgQR1Bra4worofFGiW+kyaYtoJW+02MdzJuO75mznHHSuerKlUjVipR2BO6uFFKVYAEqQD0JHWt/VNGtbPwhoupxGT7RevKJdzZX5TgYHalOootJ9dP1/QTdjAooCsVLBTtHU44pQjFC4U7RwTjgU7oBKKXa20NtO0nGccUpRlUFlIDdCR1oGNpaSikMWiiikAUtJRSAWlpKKkBaKKKQGz4U/5Gaz+rf+gmvcfD3/AB7yf7/9BXhvhT/kZbP6t/6Ca9y8Pf8AHvJ/v/0FeNmZzYjY3aKSivHOIKKKKACjFFFACUUtJQAUUUUDEopaSmAUUtJQAUUUUAFFFFAy/RRRWBIUUUUCCiiikAUUUUwExRS0UAJRRRQAh6V594x/5iX/AF7t/wCgV6Ea898Zf8xL/r3b/wBArswP8ZG1D4jwSlpKK/Qlsd4tFFFAC0UlLSAKWkoqRi0UUUgClpKKQC0UUUgNTw7rP/CP69baoIPP8gk+Vv2bsgjrg46+lanhrxpceG9Uv7yK1WZLxWzEz4CtnKtnBzj/ADiuXornqYanUu5q91Z/IlxT3N3TPEj2Gn61bSW5nk1SII0pk27DkknGDnr7Ul14jefQdH02KAwyabI8iziTO4s2RxjjH1NYdLSeGpc3NbW9/wALfkHKjtpPGej3Gow6zceHBJrCMrO4uSsTuP4ymOvH/wCuuZ1vU/7Z1q81HyfJ+0SGTy927bntnAz+VZ9LUU8LTpu8fTd7dgUUtjYOu58IroP2bpdfavP8z/Z27duP1zUj+It/h7TdJ+yY+w3LXHm+Z9/JzjGOPrk1h0U3hqfbrf5j5Ud1pfiLUNZ8cXt/Y6XHOt5AUnsHnH72MKAQGIGTxkYFbSR2ekeEtfl/4R+fR454PJR7yUtLLITwqggfKOv/AOqvLFZkYMrFWByCDgipri9urwqbq5mnKjCmWQtj8646mATa5XZad+nzs/miHC+xpalrv9o6BpWlfZ/L/s8SDzfMz5m456Y4x9TRrOu/2vY6TbfZvJ+wW/k7vM3eZznOMDH05rHorqVCCtZbXf37l8qClpKK0GLRRRSGFFFFIBaKSlqQCiiikAtFJS0gFroPBn/IxR/9c3/lXPV0Pgz/AJGKP/rm/wDKsa38OXoKWx7n4d/5B/8AwM1s1jeHf+Qf/wADNbNfJ1fjZ5VT4mFFFFZkBQelFB6UAcb4k/4/H/65f4189V9C+JP+Px/+uX+NfPVfXZF/Dl8j0aPwoWikpa942FopKWpAKKKKQC0UlLSGFFFFIDtf7e0q/srXTvF2l3kdxbRCKG8tzslEfYMjcEe9XLHQjpPi7wzcWuoNfaVdzB7SUgqVwfmUqehrLbxXpWq29uviHRXu7q3jES3UE5id1HQOMEH602bxru1bR5rfTkg0/SmBgtFlJJ5ycuRyT64rxHQraxhFq97q6a2e3Va+iMbPZI27Pr8Q+P4W/wDQzUeq6xeeEfD2gWeiSC2N1ai7uJlRS0jMehJB4HpWDD4q8n/hIf8AQs/2wpH+t/1OWLenzdfat/TFudU8LadHqXhe41aCAslpcWc+HAzyjhQcDPrioqUXTtKqrxutLrX3bdXbRg1bf+tDXuLlrzxd4EuXSNHktkdhGoVcnOcAdKi0TxLqOrfEC50O6eJtKmkuIWtBEoTADc9M5yM5qp4s1uLRvFXh6ZLWMS6ZbJ51nHL8sbcnZu55ArldI8S/2V4uOv8A2TzcyySeR5m37+eN2D0z6VnSwjq0nLl+y7bb3bX6ago3W3Q6nw7rWp2ngXxKsN9MgsTEttg/6oFznH1rPtLuaD4YPdo585NZWQN33BM5/OsvQfEltp8Gq2V/YvdWWohfMSOXYylTkEHHvUH9uhvDEugQ2TYlvPtKSeZkjjATbjn65/CtpYWXM/d3kn02tr+I+XXY702VtN42t/Fuwf2e1gdSf0EirtK/XdivLr26kvr6e7mOZZpGkY+5Oa7i+vb/AEb4W2+k3yNDc3dw3lxSKVkWAEE5B5ALVwFXgYPWb1t7q9F/X4Dgj0vxV4r1fQo9Ch0y4W3B02GR2WNSz9cAkjoMdPc1dXRLDVfHml308EKR3WmjUJoSMRmQDuPTOCfpXNXHi7RdUisY9W8OmX7HAkMckV0VZ9o6N8vQ/mOeaoz+Nb5/FUOtwwxw+QgiitxyixAY2H1GCa5Y4Sty8sI8rs7vTW+239IlQdtFY7NNQjvI7+28QeKtHvbCeJxHBGMGF8fIU+UYx9ax5rNNQ8I+CrORtqT3csbH2MgBrHute8ONFPLZ+Ggl3OrL++uDJFGT1Krgc+mTxVO68RPPoOkabFAYZNNkeRZxJksWbI4xxj6mqp4WorOKa18tNGr6eqGoPodPq/jHUtJ8WyaZYiKHS7SX7OLIRL5boDg7hjkn1q7Np9vb614t8MWyhYbi2F1bx/3XQB9o/M/lWG/i/Rrq+TVr/wAOiXVUwzOlwVhlcdHZMdfbPNZNn4pvIPF6+IplEtx5xkkTO0MCMFe+BjiiOGqcvuxs0vLWSaa/Ld9xKL7HYX1jDL4K/wCEajiH9oafbRagcdSzk71/BWWue8eSrFqtppMZ/d6ZaxwYH9/GWP5n9Kis/Gdxa+NJfEbW4k81n325fAKEYC5x247dqwtRvpNR1G5vZf8AWXErSMM5xk5xV4fDVY1Lz23+b3/rzKjFp6laiiivRNQpaSikAtFFFIApaSikAtLSUVIG14V/5Gaz+rf+gGvcvD3/AB7yf7/9BXhvhT/kZbP6t/6Ca9y8Pf8AHvJ/v/0FeNmZzYj4TcpKWivFOESjNLSUDCiijNMAooooAKSlooASilpKACkpaKYxKKWkoAKKKKANCkpaK5xCUUUUwCiiigQUUUUAFFFFABRiiigBD0rz3xl/zEv+vdv/AECvQj0rz7xl11L/AK92/wDQK68D/GRtQ+I8Dooor9EWx3hS0lFAC0UUUgFopKWkAUtJRUjFooopAFLW74b8Hav4pmYWEAWBDh7iT5Y1PpnufYZrvY/gdMYsya9GsnotqSPz3j+VcNfMcNQlyznZ/f8AkRKpGOjZ5LRXXeJvh3rXhmFrl1W6sx96eHPyf7ynkfXke9cjW1GvTrx56builJNXQUUV1fhf4faz4ojFxCqW1nnAnmyA/wDugcn+XvSr1qdGPPUdkDaSuzlaK9bPwPfysrr6l8dDacfnvriPE3gfV/Cx33UazWpOFuIclM+h7qfr+tc1HMcNWlywnr81+ZEakZOyZzlFJXfeGPhZqWv2KX1zcpYW8g3R7oy7uPXbkYB7c1riMRToR5qjsipSUVdnBUV6Nr3wi1PTLKS6sLxL8RqWaPyyj4/2Rkg/TNec1NDE0q8eak7hGSlqhaKSlrYoKKKKQBS0lFIBaKKKQwooopALRSUtSAUUUUgFroPBn/IxR/8AXN/5Vz1dD4M/5GKP/rm/8qxr/wAOXoS9j3Tw7/yD/wDgZrZrG8O/8g//AIGa2a+Sq/Gzy6nxMKKKKzICkPSloPSgDjfEn/H4/wD1y/xr56r6F8Sf8fj/APXL/Gvnqvr8i/hy+X6npUfhQUUUV7xqLRSUtIBaKSlqQCiiikAtFJS0hhRRRSAKsW97d2e77NdTQbvveVIVz9cVXoqXFPRiHElmLMSSTkk96SiikMKcrsjBlYqynIIOCDTaKTQFi6vbq+m867uZriTGPMmkLtj6moaSipUVFWQC0UUUgCiiikAtFJS0gCiiikAtFJS0hhRRRUgFLSUUgFooopAFLSUUgNrwp/yM1n9W/wDQTXufh7/j3k/3/wCgrwzwp/yM1n9W/wDQTXufh3/j2k/3/wCgrxM13ObEfCblFGKK8U4QooopgGKSlopDCijFJQAUUoopgJRRRQAlFLRQAlFGKKACkpaKYy/RRRXOSFJS0UAJRRRTAKKKKACiiigAooooADXnvjLrqX/Xu3/oFehGvPfGXXUv+vdv/QK7MD/GRtQ+I8DopKWv0ZbHeFFFFSAUtJRSAWiiikAtFJS0gCr2kadLrGsWenwnD3Mqxhv7uTyfwHNUa634ZFP+Fh6SHxjdJjPr5T4/WubFTdOjOa3Sb/AUnZNn0BZWdj4d0RIIlWCztIiST2UDJY+/Uk15JffGnUv7SY2On2oslbCrMGMjr6kggA/gce9em+ORI3gfWRFnd9lfOPTHP6Zr5hr5jJsFSxKnUrLmdzmowUrtn1JoGtWfinw9DfwoPLnUpJE/O1ujKfX+orwDx14eXw34qubOIYtpAJoB6I2ePwII/CvSvgnIx8O6jGSdq3eQPQlFz/IVj/HCFVvtHnH3njlQ/RSpH/oRp4D/AGbMZUI/C7/5odP3ajijgvCui/8ACQeJrLTTkRyyZlI7Io3N+gI/GvpHUr208N+H5rt0CWtnDkJGMcDhVH6CvFvg5GsnjZ2YcpaSMPruUf1Nej/FZyvw/vVBxueIH/vsH+lPNm62OhQfw6fi9Qq+9NROEi+NOri/DzafZmz3cxLu3hfZs4z+FevlbHxDog3IJrK9gBww+8jDI+h5/CvmXRNB1HxDfrZ6dbtLIeWbosY9WPYV9LeHNLk0Tw9ZabLKJZLeIIzgYB+lZZvQw9Bx9jpLsvzFVjGNrbnzbqGktpniabSZiT5Nz5JbpuXdwfxGD+Ne5/EjUbnRfBEj6dI1vIXjhV4ztKKf7p7cDFeU/FFBD8Q794/lLLE+R6+Wv+Fek6X4p8OeOvDQ0/VpoIrh0UT280nlneP4kPGeRnj6GujGuVSnQryjeK1fzsVO7UZDfhLrF/q3h67GoXMty0NxtSSVizbSoOCTyef515qPDR1z4l32j2ylIBey+YVH+riDnP6cD3Ir1I6v4U+Huhy29lcQscmQQRzCSWRyO/p0HJ44ql4NtU8PeGdR8YawAt1f7rqTsQhOVUe7E5/FfSuWnXdKVStTjZS0ivMlSs2112OE+Jeg6H4f1a3ttKEiTSIZJoS+5Ix/DjPOTzxn09a4erurancazqt1qF02ZriQu3oPQD2AwPwqlX0eGpzp0oxm7vqzpimkkxaKSlrYoKKKKQBS0lFIBaKKKQwooopALRSUtSAV0Pgz/kY4/wDrm/8AKuerofBf/IxR/wDXN/5VjX/hy9CZbHunh3/kH/8AAzW13rG8Of8AIP8A+BmtivkKvxs8up8TCiiioICg9KKD0oA43xJ/x9v/ANcv8a+eq+hfEn/H2/8A1y/xr55r7DIf4cvl+p6NH4ELRRRXvGwUUUUgFopKWkAtFJS1IBRRRSAWikq5p+l3+qzeTYWc9y/dYkLY+vp+NROUYK8nZAVKK9J0b4OazeBZNUuIbGM9UX97J+nyj8zXC65YR6Xr1/YROzx287xKz9SFYjJrmo42hWm4U5XaJU4t2RQpaSt/wv4R1PxVeNDYoEhjx5txJwif4n2FaVasKcXObskU2krswaK9zsfgvokEY+2Xl5cSY5KlUX8Bgn9aZqPwW0eaFjp99d202Pl8wiRB+GAf1ry/7cwvNa79bGXt4Hh9LWnr/h+/8OakbLUIwrgbkdTlZF/vKfSsuvUhONSKlF3TNU76oWlrovCPgzUfF12y2xENrGQJblxkL7Adz7fnivVLT4OeHoogLia9uJMcsZAoz7AD+prgxOZ4fDy5JvXsiJVYxdmeE0V7DrvwZgFs8uiXsvnKMiG5IKt7BgBj8c15JdWs9jdy2t1E0U8TFHRhgqRV4XG0cSv3b2KhOM9iGiuy8C+A5fF0ss80zQafC2x3QZZ267VzwOOp9xxXoUnwj8MTRvbwXV0twg5YTqzA9srj/CsMRmdCjP2bevl0JlVjF2Z4ZRWz4n8OXXhfWn0+5YSDaHilUYEiHocduhBHtWNXZCpGcVKLumaJpq6CiiirAWikpaQwoooqQClpKKQC0UUUgNnwp/yM1n9W/wDQTXunh3/j3k/3/wCgrwvwp/yM1n9W/wDQTXunh3/j3k/3/wCgrxM13RzYn4TdooorxDhExRS0UAJRRRQAUUUUAJRS0UDEpaSigAooopgFFFFACUUtGKAL1FFFYAFFFFAgpKWigBKKKKYBRRRQAUUUUABrz3xl/wAxL/r3b/0CvQjXnvjL/mJf9e7f+gV2YH+Mjah8R4FRRRX6OtjvFopKWkAUUUVIBS0lFIBaKKKQC1c0nUZdI1e01CEZkt5VkAz97B5H49KpUtZzgpxcXsw3Pq7TdRsfEGjRXdq6zW1wnQ89eqsPXsRXnF98E7ebUGls9Wa3tWbPkvBvKj0Dbhn8R+deX6H4n1jw5Kz6ZevCH5eMgMjfVTxn36107/FzxXcRiCI2iSN8qvHBlifYEkZ/CvmVleMwtRvDTXK+/wCujOf2U4v3We0+HPD9j4Z0lNPsVbYp3M7HLOx6sfy/SvMfjhMrX2jQfxJHK5+hKgf+gmvQfBGn6jY+HUk1eaWXUbtzcXBlbJUkABfbCgcdjmvGPidrKax40uPJcPBaKLZCOhK5Lf8AjxI/CuTKqcp4/mb5uW9336EUlepc0vgz/wAjnP8A9eL/APoaV694p0BPEuhvpkk5hSSRGd1GThWBIHucV4z8IrlbfxyqMQDPbSRr9eG/9lNet/EG6vrLwRqNxp0zw3Eaqd6feC7gGwe3BPNGaxl/aC5XZu1mOrf2isc5rPirQPh1px0jQ7eKW+A5jU5Ct/elbqT7dfoMV1ng3UbnV/Cen3924eedC7sAAM7j2FfMJYuxZiSxOSSeSa+mfAdu9t4H0dJOCbZXx7N8w/Q1WaYKGGoxd7yb1b3YVYKMfM8a+K3/ACUC9/65xf8AoAriq634mXK3Pj/UyhBWMxx5HqEUH9c1yVfQ4FNYamn2X5HRD4UdP4E8NnxN4lgt3Um0h/fXB/2Afu/iePz9K7X4p6xc6rqNv4V0iGWYxYknjgQsS2PlXA7AHJ+o9K2PD9tD8Ofh1Nqd5GBfzqJXRuCXIxHH+GefTLVd+HNpDbeFH1+6/eXt+0tzcTEZYgMePpxnHqa8HE4vmrPEJXjF8sfN9/68jCU9ebtseO3XgrxLZW5uJ9GuljUZLKm7A9SBnFYaqXYKoyxOAPWvdvBXxJk8Ua9Lps9gkCmNpImVyx4I4b8D19q4L4o6VBovjNbiyRY1uIluNqjAD7iDx74B/E16GGx1aVV0K8bStdWNYVJc3LJHX6V8PvDfhnQxqficrLKFDSGQny0J/hCj7x/OrOn23w28VytY2VpALjaSqrG8LkDuDxmpbHxd4Y8c6ENM1iSO2ndQJIJn8v5h3RunX8faqUvwhtEYXeha7d2swyUckN+TLtI/WvHdSV5fWZyjO+nYxv8AzNpnAeOvCB8JarHHFK0tncKWhd/vDHVT7jjn3rla3vFlhr2lagtlrl1PcFQWhkeZpEZT3UmsGvo8LzOlFylzPv3OmG2rCiiitygpaSikAtFFFIYUUUUgFroPBf8AyMcf/XN/5Vz1dD4L/wCRjj/65v8AyrCv/Dl6Ey2PdvDn/IP/AOBmtnvWN4c/5B//AAM1s96+Pq/Gzy6nxMKTpS0VmQJQelLQelAHGeJP+Pt/+uX+NfPNfQ3iX/j7f/rl/jXzzX2OQ/w5fL9T0aPwIKWkor3zYWiiikAUUUUgFopKWkAtFJS1IHZ/DDR9P1rxW1vqVus8Mds8oRicbgygZx16nivcL3WfDvhS0EVxc2dhEoysKAA49kXk/gK+ZrLUbzTZXlsrmW3ldDGzxMVYqcEjI57Cq7u8js8jM7sclmOSTXh43KpYqt7Sc2o9v60Mp0+Z3b0PrmGZLi3jmjOUkUOpx2IyK+X/ABh/yOWs/wDX7N/6Ga+mNI/5A1j/ANe6f+givmfxh/yOWs/9fs3/AKGa8zIFbETXl+plh/iZi4ycCvqLwrocPh7w7aWESgMihpmH8bkfMT+P6AV8vAkEEHBHQ1tf8Jj4k/6Duo/+BDf4162aYGri4xjCVkt/0NqsHNWR1/i/4oa0+u3Vro9wLS0gkaJWRFZpCpwWJIPGemO1dJ8MvHeo6/fT6VqzrNMsRlinChSQCAVIHHcEH614k7tI7O7FmY5JPUmvXvg14bnje48QXCFI3QwW4P8AEMgs304AH41y5hhMNQwbXKk1s+tyakIxgbXxj02K58Jx3xUebaTLhu+1uCPz2n8K8Hr3D4y6zHbaBBpKsDNdSCRl9I17/i2PyNeJwKGuI1IyCwB/OtclUlhLy7u3p/w46F+Q+m/B+ipoHhaxskUCQRB5j/edhlj+fH0AryTxl8R9bk8RXdrpl49paW0rRIIwMuVOCxOM8kHj0r3j+D8K+WTpl7rHiO4tLC3e4uJJ3wqD/aPJPYe5rycphTrValavZ2118zKklJtyPbPhn4uuvE2lXEeoMr3loyh5AAPMVs4JA4zwRx7VyPxm0WODUbLV4lANyphmx3ZcbT9cEj/gIrtPAHgg+EbOaSa4868ugvmhPuIBnAHc9Tz+lZXxpUHwpZPjkXqgH6xv/hWeHqU45jeh8Ldv6+YotKr7uxofCUIPAcJUDcZpS2PXP+GK82+Hk92fifbNI8hllefz89W+Rid34gfjT/h74+TwsZbG/jd7CZ94aMZaJsYJx3BwM/SvQB498A2U02o2zQ/bJAd7Q2bLK/qCSo/U10VqVahVqxVNyU9mvn/mW1KLlpe5z3xngafU9DigjL3EokRVUcsSyYH5k1Z8S6JovhL4Zw2l5Z28+ouNsbkfN5zcswYc4UfgcAHrVnwo03jnxdN4quofLsbAeRZRNz83UsfcA5+pHpXA/ETxN/wkniaQwvusbTMNvg8Nz8z/AIn9AKrDQqTlDDXsoay9eiCKbaj2OSopKWvojpCiiikAtFJS0hhRRRUgFLSUUgNrwp/yM1l9W/8AQDXunh3/AI95P9/+grwvwp/yM1l9W/8AQDXunh3/AI9pP9/+grw823Ry4n4TdooorxDiCjFFFABRRRQAlFFFABRRRQAUYoooASilpKBhRRRQAUUUUwL1FFFYAFFFFABRRRQIKSlooASiiimAUUUUAB6V574y/wCYl/17t/6BXoR6V574y/5iX/Xu3/oFdeA/jI2o/EeBUUlLX6StjvCiiikAtFJS0gCiiipAKWkopALRRRSA7j4aeGNN8Uape2+pLIyQwh1CPt53Yr2bR/BXh3w/L59jp0STDpLIS7D6FicfhivnLSdd1PQppJtMu3t5JV2OyAHIznHIqXUPEut6qjJfareTxt1jeY7D/wAB6V4OOy7FYmq7VLQfTX8jGdOUnvoeueO/iZa6bbTabok4uL5gVadDlIfXB7t9OB+GK8PJJOSck0lFd+CwFPCQ5Idd33NIQUFZF3StRn0jVbXULY4mt5BIuehx1B9iOPxr6X0XWtM8WaJ58BSaGVNksL4JXI5Vh/nNfLlW7DU77Srj7RYXk9rL0LROVyPQ46iuXMssWLtKLtJbMmpT5/U90i+EHhmPURdH7W8YbcLZpQY/p03Y/Gug8T+JbDwpo73M7KJApWCAHBkbsAPTpk9hXhZ+JPi8x+WdZk24xkQxg/ntzXO3t/d6lcNcXtzNcSnq8rlj9Oa86OTYmtNPEzul6mfsZN+8xt3dTXt3PdXD75p5Gkkb1YnJqOOR4ZUljO10YMp9CKbRX0Siox5ToOg1/wAZax4lsbW01KVHS3JYFE2l2PGWxxkDPQDqa9e+GF/a6t4FTTPMHnWweCZAeQGJIP0IP5g14DVvT9TvtKuhdafdS20443xMQSPQ+o9q87GZdCrQ9lS92zuvUznTUo2R7P4I+HF74X8TS6hdXkE0KxtHCIg25skctkYHHYE1yXxM1qxufH9uNi3NvYqkc6A8OQxZlz9Dj65rn7r4g+K7yAwzazP5ZGD5arGSPqoB/Wubzk5PWsMPl9b2zrYiSbtbT+kKNOXNzSZ7jqHgLw54q8PRXHhv7NbSsQ6SqCQeOUYZyP6VX8FfDvX/AA7rcd3dapAlqud8FvI7CTjGCCAMd/wryLT9W1HSpDJp99cWrHqYZCufrjrV+68YeIr2Ixz61eMh4KiUqD9cdayll+KUXSU04vvqxezna19Ds/jFrVlfajY2Fs6STWoczFTkKWx8ufXivMqM569aK9HC4dYekqSd7GsY8qsLRSUtblBRRRSAKWkopALRRRSGFdD4L/5GOP8A65v/ACrnq6HwX/yMcf8A1zf+VYV/4UvQmWx7t4c/5B//AAM1s96xvDn/ACD/APgZrZ718dV+Nnl1PiYUUUVmQFB6UUhPFAHG+JP+Pt/+uX+NfPNfQ3iX/j8f/rl/jXzxX2WQfw5fI9Gj8CFooor3zYKWkopALRRRSAKKKKQC0UlLSAWikpah7AfWekf8gax/690/9BFfM/i//kdNa/6/Zv8A0M19MaR/yBbL/r3T/wBBFfM/i/8A5HPWv+v2b/0M18lkP+8z9P1OWh8TMWikrrPA/gu58Wajl90WnQsPPmA6/wCwv+0f06+gP01etChB1JuyR1NpK7LXgDwLN4pvRdXStHpUTfvG6GVv7i/1Pavbdc1vTfB+hfaJtscMSiOCCPALkDhFH+cCi+vtJ8G+HfNkCW1jbqEiiQcseyqO5P8AiT3r548U+KL7xVqzXt2cRrlYIAfliX0HqfU9/wAq+YhCrm1fnnpCP9fecyTqyu9itrmuXfiDV59RvGzJKeFH3UUdFHsKp23/AB9Q/wC+P51DSqxVgQcEHINfTKEYw5IKyR02srI+vR90D2rz3U9a8N/DW1mhs4lm1KcmRog2ZHJ5zI38K88D8h1ruNOvI9Q0y2vIiDHPEsikejAH+tfMfimzuLHxVqdvdFzKty53OclgTlT+IINfG5XhFiKsqc3ZLddzjpQ5m0z1T4Y+JdS8S6/rF1qU5bEcflxLwkY3HhR/XqatfGn/AJFKz/6/k/8ARclZfwSsZVi1W/ZSInMcSHsSMlvyyv51P8bb1V0vS7Hd88k7S49lXH/s9dDpxWaKFJaK34Iq372yPGKtafY3Gp6hb2Vsm+eeQRoPc+vtVWvWvhF4eSGK48TXoCKqtHAX4Cgfff8Ap/31X0GNxCw9Fze/T1OicuWNzb8VSjwf4Ks/DWjq0l/dr9nhEY+ds/6x8DuSf/HvauJt/hB4mngErNYwsRnypZjuH/fKkfrXVeC9UTxd8R9U1mQEx2sHlWit/AhbGfYkZP8AwI1U8dfELXNC8YGxsJI0trdULxtGG80kBjkkZHXHGK8GjLE05+wpW52uaTf5f11OePMnaO55trnh3U/Dt2LfU7cxMwyjA7lceoI6/wA6y69/+K1pDeeAprtk+aCSKWM9xuYKf0avAK9fL8U8VR55KzTszelPmjcWikpa7zQKKKKkBaKSlpDCiiipA2vCf/IzWX1b/wBANe6eHf8Aj3k/3/6CvCvCf/Iz2X1b/wBANe7eHP8Aj2k/66f0FeFm269DlxPwm5RS4pK8Q4QooooGFFFFABSUtFABSUtJQMKKWkoEFFFFMApKWikMSlopKAL1FFFYgFFFFABRRRQAUUUUCCkpaKAEooopgB6V554y/wCYl/17t/6BXoZ6V554z/5iX/Xu3/oFdeA/jI2o/EeBUUUV+krY7xaKSloAKKKKQC0UlLSAKKKKkApaSikAtFbOm+FNc1i0F1p+nTT25YqHUjGR1HWqN/pl9pNz5F/aTW0uMhZUKkj1HqKxVanKXIpK/a4rrYq0UlW9N0641bUYbG0QPPM21FLAAnGepqpSUE3LYdyrS1oRaFfzW2o3CRKYtPIFyd4+Uk449eR2rOqYzhO/K9guLRRRTGFLVy10i7u9Mu9RhjU29ptEzFgCNxwOO9UqhTUm0nsIWiiimMKKKKQC0UlXdP0q61NLp7aMMLWBp5ssBhF6nnrUTkoK8hFSikpaBhRRRSAWikpakAooopAFLSUUgFrofBf/ACMcf/XN/wCVc9XQ+C/+Rjj/AOub/wAqxxH8KXoKWx7t4c/5B/8AwM1s96x/Dn/Hh/wM1s96+Mq/Gzy6nxMSiiisyAoPSig9KAOM8S/8fj/9cv8AGvnivofxL/x+P/1y/wAa+eK+yyD+HL5Ho0fgQUtJRX0JsLRRRUgFLSUUgFooopAFFFFIBaKSlqWB9ZaSyjRbL5h/x7x/+givmjxh/wAjlrP/AF+zf+hmsSlrx8BlX1SrKpz3v5W/UyhT5He4V9R+E7a3svCumQ28SRp9mjchfUqCSfckmvlyitMxy941RXNy28r/AKodSnzq1z6v1DSdL1UINQsrW6EedonjV9ueuM9Kp/8ACIeGP+gFpn/gMn+FfLlLXlrIKkVZVbfL/gmfsH/MfSWueFfDsOg6hLFommpIltIystsgIIU4I4r5toor08BgZYVSUp81/wCu7NacHHrc9Z+GPj+1sbRND1icRIrEW1w5+UA87GPbnoenbjivStT8N6D4haO4vrG2umC/JL3K/wC8Oor5cqzb6heWilbe7nhU9RHIV/ka4sVkyqVXVoz5WyJUbu6dj6avNS0PwjpIErwWVrEvyRIAM+yqOSa+e/F/iWXxTr0t+wKQAeXBET9xB0z7nkn61iSSyTSF5ZGdz1ZjkmmVtgcrjhZOcnzSfUqnSUdeoVettX1G0s57O3vZo7adSskSudrD6VRor0pwU9GjQ7b4Y+I7bw/4lYXsgjtryPymkPRGyCpPt1H416f4i+Hek+KdZi1Z7uaPcqiQQkFZVHQg9uOM/SvnurEV/eW8Rihu544z1RJCAfwFeXisunUre2oz5ZWszOVNt80XY9j+LXiWzi0P+wYJUkup3Uyqhz5aKc8+hJA49M14rQTk5J5orowWDjhaXIncqEFBWCiiiuosWikpaQBRRRSAWikpaQzZ8J/8jPZfVv8A0A17t4c/49pP+un9BXhPhP8A5Gey+rf+gGvd/Dn/AB7Sf9dP6CvBzfden6nLifhNyilpK8I4RKKWigBKKKKYBRRRQMKKKKACiiigAxSUtFACUUUUAFFFFMC7RRRWAwooooAKKKKACiiigAooooEFJS0UDEPSvPPGf/MS/wCvdv8A0CvQz0rzzxn/AMxL/r3b/wBArsy/+MjWj8R4FRRRX6WtjvCiiikAtFJS0gCiiikAtFJS0gCiiipA7i6mkh+EeltFI6E6lJypx/CadplzN4g+HutW+oSNcPpfl3FtLIdzICcMuTzj2qS0tbbWvhtY6cmsaVaXUN7JKyXl0sR2kY6de9Vby503wz4VvNFs9Qh1DUNRdPtU1tkxRRqchVY/eJPevATUrwivf577bK+rv6GXl1uMbQ9B0LTLCbxBLfy3l7EJ1gs9gEMZ6FiwOSfStTRNAi0T4ieHpbS4a50++/f20rDDFdpyGHqKh1Ozh8bWWl3thqNjDc29oltdW9zMIihTowz1U+1XotY0u28X+FdPhvoJLXSIzHNeFwsTOwJYhjxjOOaznOpKLV25NS5l20dv0t3E22vvKenf8i749/66J/6NasmzsfCsFjaNqFxqF7d3C7pI7HaFtx6HcDub9KvWV/ZpoPjSNruBZLmRDApkAMv7wn5R/Fx6VsRy3EugaV/wjXiDTtLs47cC8VphFKJv4mbjc3tROc6fMtVdry+yutmx3sZVt4JtE+IkegXE8slnLEZkkXCvtKFhn39axtR0uwu5xb+F7TVr1oARcSPDuDHPBUICQOvWu3m1fTT8WrK//tS0ltfsIVrnzl2bvLYcnPBz2PPNeZw6he2NxM9jeT25cnc0EpTcM98HmtsNKtVkpX15Y77Xd73HHmep23hayht/B/im31tbm0iRrczKI8Sj5iQAGxgnjr61lX+haLfeGrjWtAkvF+xyKl1b3e0sA3AZSoHGaveGbmHU/C/iOz1DWIILu8aARyXk/LkE9SeccAZ7USQQ+FPBeqWNxfWlzqGpvGqxW0wlEcanJZiOBms7yhWlZvmclp0asr/r6C1T8yjZ6f4Tht7OO9udRvbu4QNKbHaEgz2O4EsR37VZi8DRjxxd6HNdSG2toTOZI0zJJGFDAKP73OK33nnl07TG8O+I9P0vSYoEFwnnCKRZB98sANzE9vWqmuTaZqfxHuZ4tfW0ZrZDa3sEw2eaFAAdh90dc1lHEVXKVm1o/OzuraW09FcXMznb+x8Mz6Tcz6Zc3tpe2zAG1viuZgTj5SoGCO4q2vh7RtI02wm1z+0p7q9iEyw2SqBDGehYsDknritfWp5T4Uvl8UX2l32ofILB7d0eYHPJLIPu49aunW9V1vR9Ml8PeIbeykgt1hurSeZYyrLxvG7qCKHWq8iSel9XfTbva9vlvpcOZ2MD/hA4h4oa0e9YaUtr9uNyUw/k4z0/vdv84rS0AeG203xK+inUY5V0qdWju9jB1x94Feh9j60yx1lV8TXthrPiCK9S8sGtDfL/AKuJzyBnuoOeenNM0fR4fDWn+IDfazpZmuNNmht4oblXaQkdR9eMDqairKco2qSd7K1uuuvT/hgu+p59RSUte8jcWikpaQBRRRSAWikpakAooopAFdF4L/5GOP8A65v/ACrna6HwV/yMcf8A1zf+VYYj+FL0E9j3fw5/yD/+BmtrvWN4c/48P+BmtnvXxlX42eXU+JhRRRWZAlB6UtIRxQBxniX/AI/H/wCuX+NfPFfQ/iX/AI/H/wCuX+NfPFfZ8P8A8OXyPRo/Agooor6E2ClpKKQC0UUVIBS0lFIBaKKKQBRU1rCbm7htwwUyuqAntk4rr7vwFaWt9Jp3/CUacL9CB5EyvGNx6DcRjvXNWxNKi1Gb1fk3+QnJLc4uirOpabd6TqEtjexGOeM4ZT/MHuKrVpGUZq62HuLRW1YaEl54V1TWDcMrWMkSCILkPvOOvajxDoKaJFpbpO0v220S4IK42Fu3vWX1im5+zvre34X/ACYuZXsYtFFFajFopK29e0NNGtdJnWdpDfWouCCuNhJ6e9ZTqQhJRe7C/QxaKKKsYUtJRUALRRRSuAUtbN7oSWnhXTdZE7M15JJGYtuAmw9c96xainUjUV4+a+4SdxaWkoqhi0UUVIBRRRSAWikpaQBRRRSA2vCf/IzWX1b/ANANe7+HP+PaT/rp/QV4R4T/AORnsvq3/oBr3fw5/wAe0n/XT+grwM43Xoc2J+E3aKKK8I4QpKWigBKKWkoASilopgJRRRQAUUUUDCiiigAooooATFGKKWgC5RRRWIwooooAKKKKACiiigAooooAKKKKBCHpXnnjP/mJf9e7f+gV6Ieled+M/wDmJf8AXu3/AKBXZl/8Zf11NqPxHgNLSUV+mLY7xaKKKACiiipAWikp8cbyyLHGjO7kKqqMkk9hSYDaK6ofDnxSWjX+zRufHHnoWTPTcAcr+NUdL8Ia3rKXDWFn54t5hDIA6gqx+p6cdelcv1zDtOSmrLzRPNHuYlFbVv4T1u71S5023svMntjich12R/Vydv61BrHh3VNBaIajamJZRmORWV0f6MpIprEUXJRUld9Lj5kZlFdHbeBPEd3Zx3MWn/LIm+NGkRZHX1CE7j+VZum6Fqer372NlZvJcRgmRThRGB13E4A/GpWIoNNqS031Wgcy7mdS1q6x4a1bQVikv7XZDLxHKjrIjewZSRn2qDSNHvdcvvsVhEJZ9jPs3AZAGT1p+1puHtFJcvfoF1a5RoravfCet6ffWllcWLi6ul3Qwowdj9QCcfjUmo+Ddd0q3Fxd2QEO8I0iTJIEY9m2k7fxqPrFB299a7arUOZdzCors9c8CSaV4UsdUWSMzkObkfaY2XAbC7Mfe98E1k6Z4O13WLNbq0sswOcRvJKkfmH0XcRu/CsoYyhKHPzK17CU01cwqWtWz8M6vfX93YwWTm7tULzQtwwAOOAep5HAqxqPg3XtJsftt5ZBYAwV3SVH2E9AwUnH41TxFFSUXJXfmPmXcwqK7XVPAUmn+D7XVRJEbrdIZ1+0xlNg6bMdT6gE1ja3azxadopfS4LUS2+6OWFtzXAz95h2NZ08XSqW5HfVr7gUk9jDpa6M+AvEq2pnOmtwnmGLzU80L67M7v0qhpXhrVdbguJdPtDOIWVZAGAILHjg/wCRT+s0GnJSVl5hddzLorY1jwtrGgwRz39oEhkO1JY5FkUn0ypODVPTNNutY1GGwskD3ExIRSwXOBnqfpVKtTlHnTVu47q1ynRW7eeDtesZbSGfT3Et2SsMasGdiPUA5H40uo+Ddd0qxe9urNfs8ZxI0UySeWfRgpOKj6zRdrSWu2ouZdzCortLrwHLb+C49V3x/a/NYyL9pjKeUFyMere2c+1Ymk+FNZ1u2a5srQG3DbfNkkWNS3oCxGT9KiOLoyi5cysnYFNPUx6KsX+n3emXj2l7A0M6HBVhyP8AEe9V62UlJXRSYUUUUALRSUtSAV0Pgr/kY4/+ub/yrnq6HwV/yMcf/XN/5VhiP4UvQUtj3jw5/wAg/wD4Ga2axvDn/IO/4Ga2a+Kq/Gzy6nxMKKKKggKD0ooPSgDjPEv/AB+P/wBcv8a+dq+ifEv/AB+P/wBcv8a+dq+z4e/hy+R6NH4ELRSUtfRGoUUUUhhS0lFIBaKKKkApaSikBd0r/kMWP/XxH/6EK3viN/yP+rf76/8AoC1zljOttf287glYpVcgdcAg12ur654J1XXbjWZ7XW5ppWDm3bykiYgAYJBLY4rzcS5QxEZqLa5WtO90Q9JXNi80aHxF4g8JR3xY+bpay3ODhnVAT19+lZej61p3iHxAuh3Og6bBp905igaCAJNEedrb+pPTOetZH/Cb3v8AwmMPiAwoBDiNLZThFhxjYPwJ59a0LTWfCGi6m2t6bFqU14NzW9pMiCOFz3LA5IGeK4HhqsIcs4tvl923SV2/8tfIjlaQ+xtTZeAfF9qW3GC7hjJ9drkf0p/ihIZLzwgk9rNdRNp0IaCE4eTk/KPrWJaeI4o/C+uadcrM93qM8cyuqjZkNlsnOf0NaD+M7RNa8OahDbzONLtUgmSQAbyMglcE+vGcU5UKyqOXLfV/+kpfmVZ3v/Wx1mn6RcapfXGnar4e0axsHikMUcLRC4hIGVPytuJ9c1gaTd2mjfDg6i2k2N5djUjFG1zFuC/J1I79+M9807TPEPg3Q9ek1e3GsXU0/mZWRUHk7wc9/mPOOtc/Lrtq3gj+xFSbz/t5ug5UbNm3GM5zn8PxrGnh6snytPlvHuu9+rfa5KTEOhf2jaS6qdY0O2Mu+Y2v2gpIvJO0Jjj2GfSuq8Qa+ujaJ4Y8vS7C6nfTkJlu4fMwufugHgd8nrXmtdxda14V1zTNItNSGp281hbLCZokRg+OowT+R9+ldeKoS5oOacopvZbafiVJaq4/W9M0wa/oGoW+lPJa6pAs76fbnGX6FV9ATj9a6C10i51QajZaxoWi2dutvJJAtsYhcQsoyv3WLH3zXNnxxbQeLtM1C1snGmabD9mhhYjzCmCC3pu5zVrS9f8AB+gahc3loNXuZbqKSPc6p+6DD0z8xz3zXFVp4jkS5XdLTdvf10aVu9yWpWDwnaxyeGGfRrTTL7XzORLDehWYRY48tXIB9zUb6NBr3jnS9On0Z9ImlX/TYFXYjEAktGOwIGKx9Nu/Ddzo8djq0FxaXUMhZLy1jVzIp/hcEjp2IrQ1HxrFFrOiXOlxzyQ6RH5aSXRHmTg/e3Y6DHArWVKv7WXInd31fTTTW9muytdDs7ux1sOive6jNp1/oehWuiuHSOSGSMTxcHa24NuJ6ZrmY0tPCfg+21EafZ3uoX9zJGsl1GJEjjQ4+VTxknvVO6uPBE1zPqAi1Rnl3OLDCKiuf9sHO3PtmmabrejXnhtND10XUSW0zS2txbAOU3feVgcZFZwoVFG7T5bq6s13823ra/fzEkzS8V3seo/D3w/cx2cFpvnm3RQDCBs8kDtnriuCrqfEWu6PeeG9M0fSYr1EspZGL3O3Lhu/B657Y4rlq9DAwcKVmrav82aQVkFLSUV1li0tJRSAWiiipAKKKKQC0UlLSA2vCf8AyM9l9W/9ANe7+HP+PaT/AK6f0FeEeE/+Rnsvq3/oBr3fw5/x7Sf9dP6CvAzjden6nNifhN2iiivBOEKKKKACiiigAxSUtFACUmKWigBKKWkpgFFFFAwooooAKKKKALtJS0VgMSiiimAUUUUAFFFFABRRRQAUUUUAB6V554z/AOYl/wBe7f8AoFehnpXnnjP/AJiX/Xu3/oFdmX/xl/XU1o/EeAUUUV+nLY7wpaSikAtFFFIArpvh75I8d6V5+3b5h27um7adv64rmasWLIuoW7SXElugkUtNGuWjGfvAcciufE0/aUZQ7poTV1Y7bwc18fi2huDN9oNxN5+c56NnPt0p1tLJF8PfFpjdkLajGp2nGQW5Fb1n4qbTbr+07/xdYX1tGhKRWtvtuLk4wqyfKCB9T2rzNtavvsV7ZJNstLyYTTRbVO5gcjnGRj2NeJRpVMRNy5UkuXvbRtvdL+upkk5P7jpIi6/CGb7HnJ1IC82ddu35c+2cUtiJX+E98s+Sn9oRCz3/AN/+Pbn2rnNG1/UtBmkk0+48sSjbLGyh0kHoykEGnax4j1PXfKW+uA0UPEUKKqRp9FUAV1PB1Oe2nLzc1+vpa3y32K5Xc7W5ubHVvE1lFrEOpaJ4jURRJLAVki3ADYxXqO3Q4qnZTaho154ni1Szk1OxdvK1GeFwjqdxw4Pue2MVkW/j/wARW1vHEt5G7RLsinlgR5UHoHIzWdpviPVdJvpry0vHE0+fOLgOJc9dwbINYxwNZJppWsrK76O+jtdeWrsxcjN7VrKA+Cvtmh6neSaOLsJLZ3aKHjlI4YEcHj0pfhgzJ4vZlJDC0mII7fLWHrHifVdchit7udBbRHckEUaxxg+uFAyfrVTTNVvdHuzdWE/kzlGj3bFb5WGCMEEVvHC1Xhp0pWu7/j3dlf1sVyvlaOv+GjRyavrEs4mluPsErII2xKxJG7aT/Fikstd8PWGl6vbaZpesObu2aOXzpFdE9HYADGD3rjLK+utNvEu7OZoZ4zlXU4IrY1Hxrrup2clrPcxxwzf64QwpGZf94qATWNbASlWct07dWrW8raicbs1PEf8AyTrwmfe4/wDQ63vGM3h23k0gX1pqkka2MRtntpkSLbjsCDznr+FcLF4l1OLQX0UTRvYsSQjxqxTPJ2sRkfhVjTfGWt6ZYpZQ3CSW0ZzFHPEkgjP+zuBxUTwNV2t0cnu1dN37aNByM7ey1pdV17xDf29tc2brobL+/OJCQBh8j1GOa53wgxbwl4wQklfskbYPruPNYP8AwkusG7vLtr12nvIjDO7qrb0PVeRwPpjFVrPVb2wtbu2tZ/Lhu0Ec67FO9QcgZIyPwxRHATjFpW15fwt/SGoaW9Dp9X/5JX4e/wCvu4/nW/GLf+1Ph2Lnb5X2Yfe6Zz8v64rhLbxJqdpos2kRyo1lISTHJGrbSeCVJGR+FVbrV769hs4Z7gulnH5duAoUouc4yACfqaHgqsvdbsryf3p/lcXI/wAzpLd9V/4WqDmX7d/aOG6527ufw2/pW9dSRw6T8QTZvsj+0xqChwMFyCPp1rlz8QPEbWxi+3L5hTyzcCJPOK+m/Gfx61jQatfW2n3djDPttrwqZ0Kg7ypyOSMjn0NS8FWm05WVuVaeTT7eWgcre50tiS3wm1MMSQmoxlQe2Vqv8N/+R90v/fb/ANAasGPVbyLSZtLSfFnNKJXi2Lyw6HOM/rTdO1G70m/ivrKXyriIko+0NjjHQgjvW7w0vZ1YfzXt81Yrldmu50vh22vdU8a3zR6jPbMnnTTzx/NIUH3gvqT0rb8Nvo0mi+J10m11FEGnv5kt1Krq3pwqjB69/WuDsdXvtN1NdRtLlobsMW8xQOc9cjoQfSti58e+IrmKWE3sccE0bJJFHBGqMG65G3r79a5sRg6s3aNradbbP018iZRbLl1/ySXT/wDsJv8A+gVua9JoEXhfw0NQtdRmtzZgxNayqke/+POQfmzXEWPiTU9P0m40uCZDaT5LRyRK+CRglcjg49Km0rxbrGj2Zs7edHtC24QTRrIit6gMDj8KU8HUe3STe7W/y0aBwZb8ZaxbaxPp729nd26wWqxBrogvKoJ2tkdeO9czVvVNWvtYvDdX87TzEBQSAAoHQADgD2FVK7qFL2VJR/4JcVZWFopKWtSgooopALXQ+Cv+Rjj/AOub/wAq52ui8Ff8jHH/ANc3/lXPiP4UvQT2PePDf/IP/wCBmtqsXw3/AMg//gZrar4mr8bPLqfExKKWkrMkKD0ooPSmI4zxL/x+P/1y/wAa+dq+ifEv/H4//XL/ABr52r7Th7+HL5Ho0fgQUUUV9EbC0UlLSEFFFFIYUtJSjkgetIAortJ/hxeWt6lrc6xpNvLMqmBZZirS5HGBjI54ye9YkPhfVZ/EE2irCou4Cwl3MAkYHVi3Zcc5rjjjaE03GS0V/kSpJmNS10V94PuLTT2v7fUtP1C0idUuJLSUv5JJwCwIBx7itjxD4U0mw8GaXf22oWRuWEpeRGc/a8MAAgIwMfhWf1+jeKTvzO23W19f6/AOdHC0VoaFpE2v6zb6ZbyRxyzkhWkztGATzj6Vp3/g69s7+10+K7sry/uHMZtrabe8RHZ+w/8ArGtKmJpU5+zk7Pf5D5knY5yiuouvBF5DZ3M1tqWm30tope5t7WctJGB1OCBkDvitO78J6VD4Ah1BNSsTdee2bgM+JAFyIgMY3Z9vxrCWYUFbld7uwudHC0Vv6d4Sub3TY9Rub6x061mYrC95KU80jrtABOB61Nb+CNVn8SHQ2MMdwYTMj5zHIgGQVI6g9qt4ygm05bb/AC3+7qPmRzdFdPP4IvI9PubqDUdMu5bRN9xb20++SJe56YOO+DWnZeFNJn8AT6hJqVit3564uGZ8RArkxEYxu/D8aznj6KSad9baeYudI4Wiuj1O31FfB2hyTNbNaSvMLdYo8Sg7udxxzz0q0ngC/LR28+paXbajIoaOxmuMTHPQHjAJ9M0/rlKKvN21f4O3/D9g5l1OTorf0jwfqWr3eo2ieVBc2CbpI52298EZ6D6nin6j4QubLR31S31HT9QtonCTm0mLmInpnIHHvTeLoqXJza/57fePmV7HO0Vb02xk1TU7WxidUkuJVjVn6Ak45rc1DwPf2F3DZLe2Nzfyz+StpDNulXrhmH8IwM80VMTSpy5JOzByS0ZzFLXUTeBrtLe6a21LTb2e0QvPbW85aRAOp5ABx3wa0tH8KaVeeBr7UJ9Rslug8RWZ2cfZs9UYAYyfoawnjqMY8yd9UvvE5o4Wit3SvC9zqVi+oS3dnY2KyeWLi6kKq7eigAk1V1vQbvQbmOG5MUiTIJIZ4H3xyoe6mtFiKUp8ieo+ZXsZlLSUVsULS0lFIBaKKKkAooopAbfhP/kZ7L6t/wCgGvd/Dn/HtJ/10/oK8H8J/wDIz2X1b/0A17x4c/49pP8Arp/QV8/nG69P1ObE/CbtFFFeCcIUUUUAFFFFABRRRQAUUUUAJiilooATFJS0UAJRS0lMAooooGXaKKKwGFJS0UAJRRRTAKKKKACiiigAooooAD0rzzxn/wAxL/r3b/0CvQz0rzzxn/zEv+vdv/QK7Mv/AIy/rqbUfiPn+lpKK/T1sdotFFFIYUtJRSAWiiikAUUUVIC0UlLSAKKKKQC0UlLSAKKKKkApaSikAtFFFIBaKSlpAFLSUVIxaKKKQBS0lFIBaKKKQBRRRSAWikpakBaKSlpAFdD4K/5GSP8A65v/ACrnq6HwV/yMkf8A1zf+VYYn+FL0FLY958N/8g//AIGa2qxfDf8AyD/+Bmtqvh6vxs8up8TCiiisyBKD0paQ9KYHGeJf+Px/+uX+NfOtfRXiX/j8f/rl/jXzrX2vDv8ADl8j0aPwIWikpa+jNQoooqRi0UlLSEFKv3x9aSipktBnd+Pyf+FiW/8A1zt//QRXVRyxHx/4ztfs8dzcz2iiK3dyvnYRSyAjnkeleQ3F7d3VyLm5up5pwABLJIWYY6cnnilbULx777a13cNd7g3ntKxkyOh3ZzmvHnlkpUow5to2+d0/0M+TSx27arcQaDq8dl4MGnwSReXczb5PkGeOH4yDUGvRSzfDXwvJHG7xxm4EjKpIX5+57VzWoeINY1aFYr/U7q4iXkJJISufXHrUUOsalBp0mnRXs6Wchy0IkOxvwpwwNSHLJWupX3b6W3evUFFm78OTjx7pf+83/oDVpeAbiGD4jTeds3yCeOLecAuc4Ge2eR+NcTb3M9nOk9tNLDKnKyROVZfoRyKjMj+Z5m9t+d27POfXNbV8G6sptu3NG35/5jcb3PR7LUbnTL28+w+A1t7iOGRZpPNkwqY+bO44x/PtWbNFLcfCOz8mJ5BHqbl9ik7Rs6n0rnbrxLrd9Zi0utWvJoOhSSViD9eefxqC11jUbG1mtbW9nignGJI0kIV+Mcj6VywwFRWlpdNPdva/V+ouRno2rXNq/hTw5dJ4bj1e3W0ERkEkg8mQfeUhDxk+tT6LqF7feNbKO80kaX5GlSpDFuJJjwdpOefzrzXTdc1XSAwsNQuLZW+8sblQfqOlRf2rqIvXvRf3IupAQ8wmYOwPBBbOazeWTtKN+9nd9fK9v8xez6HUfD7/AF+vf9gmb+lP0yGS4+FGqRwRPK66jGxVFJIG0c4FchbXd1aGQ21zNAZEMb+U5Xcp6qcdR7VPYavqGliQWF7PbiVcOI3Khh71vVwU3Jzi+sX9xTjfU7Vp4LXwr4GmuMeTHeSvJn+6JBmqviPQdZuviLN5FrPKbi5EsEyKSpQkEMG6YA/LFcdJd3M1vFby3Mz28JJjiZyVTPXA6DPtV6DxJrdtY/YoNWvI7bG3y1lYAD0HPA9qzWCqxfNBq/vb+bv+H4hytao9EvbmC61zx7JburINPCFl6FlADfqDXM+Gv+RA8W/7lv8A+hmuTgu7m3SZIbmaJJ12SrG5USL6NjqPY0RXl1BbzW8VzNHBNjzY0chZMdNw6HHvRHAShBxT6x/8lt/kChZfcavhD/kcNI/6+4//AEIVtQ6ZHrHxWurKa4kgR72bLxttY4LHAPYnGK42GaW3mSaCV4pUIZHRirKR3BHSnm6uGujdGeU3JfeZS537uuc9c+9bVsNKVRzi7Xjb/gjcbu56t4VjA1bU1g8Jtp0MVrMhuJHlZ84+6SxwSfYVzOgwy3Hwz8RxQRvLJ9pt22IpJxnrgVgT+KNfuHRpdav2aMEKfPYEA8Hoaq6fq2oaS8j6feTWzSLtcxuV3D3rkjgasU3dXbi929nfd66k8jPQRcW7fDrQ5E0FNXS2aWOdd8gMLls5IQ9x3Nc94u1G7utO0i2m0IaVbwxubdNzEspIz97kDI/WsDT9Z1LS5XlsL6e3d/vmOQru+vrUd5fXWoXDT3lxNPKeskrlj+Zq6ODlCrzPVXb3fW/S9uu41CzK9FFFegaBS0lFIBaWkopALRRRUgbXhP8A5Gey+rf+gGvePDf/AB7Sf9dP6CvB/Cf/ACM9l9W/9ANe8eG/+PaT/rp/QV89nG69P1ObE/Cb2KSlorwDhEooopgFFFFABRRRQAUUUUAFFFFABRiiigBKKWkxQAmKKWimBcopaSsCgooooAKSlooASiiimAUUUUAFFFFAAeleeeM/+Yl/17t/6BXoZ6VyevaRNc3EkyKsqOuGjI7Yx+NdWDmoVVKRrSaUtT5oor0jWfh/bTM72DG1m7xPkoT/ADH6/SuF1LR7/SZdl5btGCcB+qt9D0r9Fw2PoV17r17dTuTTKNLSUV2ALRRRSGFLSUUgFooopAFFFFSAtFJS0gCiiikAtFJS0gCiiipAKWkopALRRRSAWikpaQBS0lFSMWiiikAUtJRSAWiiikAUUUUgFopKUc1IC10Pgr/kZI/+ub/ypNK8IajqO2SVfssB/ikHzH6L/jiu+0HwpbWDZs4Gknxhpn6/4CvNxmLpRg431IlJJHeeG/8AkH/8DNbVZuj2jWdoI3YM2STitKvkKjTm2jzZu8mwoooqCApD0paD0oA4vxN/x+P/ANcv8a+da+mdb02a4uPPiw2FwV715lrPgG0uGd7PNnP3QjKH8O34flX1OSY6lQi4ze9jvozXLY8zorR1TQtR0h8XduypnAlXlD+P+NZ1fXQqRnHmi7o3FopKWqAKKKKkYtFJS0hBRRRSGFLSUUgFoooqQClpKKQC0UUUgCiiikAtFJS0gFopKWpAKKKKQC0UlLSGFFFFIApaSipAWiiikAUtJRSAWlpK3dK8Kalqe1yn2eA/8tJRjI9h1NZ1JxgrydhXsN8J/wDIz2X1b/0A17x4b/49pP8Arp/QVw2heErXT5Fe3iae5H/LZ+2fTsK9C0aze0gKuQWZt3HavmczxEKr905cRNNWNWiiivGOMKKKKAEopaKYCUUUUAFFFFABRRRQAUUUUAFFFFABSUtFAFyiiisSxKKWkoEFFFFABSUtFACUUUUwCiiigAqN4g9SUUAZV5pkNyuJIwT2buPxrnNQ8OsY3QItxCw+aNwDkfToa7gjNRPCGrWnXnTfusuM3HY8J1n4f20zO+nsbWbvE+ShP8x+v0rhdS0e/wBJl2Xlu0YJwH6q30PSvqC80yG5XEkYJ7N3H41zmoeHWMboEW4hYfNG4ByPp0NfRYLP6kLRqarz/wAzohWT3PnSlr0jWfh/bTM72DG1m7xPkoT/ADH6/SuE1LR7/SZdl5btGCcB+qt9D0r6fDY+hiPhevbqbpplKiiiuwoKWkopALRRRSAKKKKkBaKSlpAFFFFIBaKSlpAFFFFSAUtJRSAWiiikAtFJS0gClpKKkYtFFFIApaSjrSAWjrXQ6T4P1HUdskq/ZYD/ABSD5iPZf8cV32h+ELSyYG1tjNMOs0nJH07D8K4MRj6NHrdkuaRwOleD9R1HbJKv2WA/xSD5j9F/xxXfaH4QtLJgbW2M0w6zSckfTsPwrsbTQUXDTne390dP/r1uQ2iooVVCgdABXz2KzWdTSOxyzxHRGHZ6Ci4ac72/ujgf/XrchtFRQAoUDoAKsqgWnV5U6kp/EzmlNy3GqgUcUtLRUEiUUUUAFFFFAEbxB6oXWnRTriRA3oe4rToIzTTa1Qk2tjjb7QG2MEAljIwY3HUfyNcBrPgG0uGZ7PNlP3QjKH8O34flXtjwhqo3WnxTriSMN79xXoYXMa1B3izohXa3PmXVNC1HSHxd27KmcCReUP4/41nV9GXugtsYRgSxkYMbjqP5GuA1nwDZ3DM9nmzn7oRlD+Hb8Pyr6jB55Tqq1XR91/kdUKkZbHmdFaGqaHqOjvi7t2VM4Eq8ofx/xrPr3KdSM1zRd0aBRRRVDFopKWkIKKKKQwpaSikAtFFFSAUtJRSAWiiikAUUUUgFopKWkAtFJS1IBRRRSAWikpaQwooopAFLSVvaT4T1LU9rlPs8B/5aSjGR7DqayqVIU1ebsK9jCrd0rwnqWp7XKfZ4D/y0lGMj2HU13mieDrKyZTDAbm4H/LWQZwfYdB/OuztNDHDTtuP91eleNis3jDSn/XyMp1oxOM0TwdZWTKYYDc3A/wCWsgzj6DoP512dpog4ac7j/dHStqC0WNQqqFUdgKtLGFrwa2LqVXds5J15S2KsFmsahVUKB2Aq2iBRxTqK5DISilooEJRRRQAUUUUAJS0UUAJRS0lMAooooAKKKKACiiigAooooAuUUUViWFFFFACUUtJQIKKKKACkpaKAEooopgFFFFABRRRQAYzUTwhqlooAyrzTIblcSxgns3cfjXOah4dYxugRbiFh80bgHI+nQ13GM1E8IatadadN+6y4zcdjwrWfAFtMzvYMbWbvE+ShP8x+v0rhdS0e/wBJl2Xlu0YJwH6q30PSvqC80yG5GJYwT2buPxrm9Q8OsY3QItxCwwY3AOR9Ohr6LBZ9OFo1NV5/5nRCunufOtFekaz4Atpmd7Bjazd4nyUJ/mP1+lcLqWj3+ky7Ly3aME4D9Vb6HpX02Hx9HEL3Xr26nQmmUaWkorsGLRRRSAKKKKkBaKSlpAFFFFIBaKSlpAFFFFSAUtJRSAWiiikAtFJSjmkAUo5roNJ8HalqO2SVfssB/ikHzEey/wCOK7/QvB9nZMDa2xmnHWeXkj6dh+FefiMwo0et3/XUTmkcDpXg/UdR2ySr9lgP8Ug+Yj2X/HFd9oXhC0smBtbYzTDrPLyR9Ow/Cuzs9BRcNOfMb+6OB/8AXrchtFRQqqFUdABivnMXm1SrpHY5Z4jsYdnoKLhpz5jf3RwP/r1tw2iooVVCqOgAxVpUC9qdXkTqSm7yZzSk5bjFjC9qdRRUkhRRRQAUUUUAFJS0UAJRS0mKACiiigApCM0tFAiJ4Q1UbrT4p1xJGG9D3FadIRmmpW1Q07HHXugtsYIBLGRgxuOo/ka8/wBZ8A2lwzPZ5sp+6EZQ/h2/D8q9teFWqjdafFOuJIw3oe4r0MLmNag7xZvCu1ufM2p6HqGkPi7t2VM4Ei8ofx/xrOr6LvtBbYwQCWMjBjcdR/I1wGs+ArO4Zns82c/dCMofw7fh+VfT4PPadXSpp5r/ACOqFSMtjzOitHVNC1DSHxd27KmcCVeUP4/41nV7kKkZrmi7o0FopKWqAKKKKQwpaSikAtFFFSAUtJRSAWiiikAUUUUgFopKWkAtFJS1IBRRW9pPhLUtU2yFPs8B/wCWkoxkew6n+VZ1KsKavN2QXsYVbuleE9S1Ta5T7PAf+Wkoxkew6mu90PwdZWTKYYDc3A/5ayDOD7DoP512dpoY4ac7j/dHSvExWbxhpT/r5GU60YnGaJ4OsrJlMMBubgf8tZBnB9h0H867O00McNOdx/ujpW1BaLGoVVCqOwFWljVa+frYypVd2zjnXlLYqwWixqFVQqjsBVpYwtPorkMQooooAKKKKBhRRRQAUYoooAKSlpMUAFFFFABRRRQAlLiiigBKKKKYBRRRQAUUUUAXKKKKxLCiiigAooooASilpKBBRRRQAUlLRQAlFFFMAooooAKKKKACiiigAxmonhVqlooAy7zTIblcSxgnsw6j8a5vUPDrGN0CLcQsPmjcA5H06Gu4IzUbwq1a0686b91lRnKOx4TrPgC2mZ3sGNrN3ifJQn+Y/X6Vwuo6Pf6TLsvLdowThX6q30PSvqC80yG5XEkYJ7N0I/Gub1Dw4xjdAi3ELDDRuAcj6dDX0OCz6cPdqarz/wAzphXT3PnWlr0fWfAFtMzvYMbWbvE+ShP8x+tcLqWj3+ky7Ly3aME8P1VvoelfTYfHUcQvdevbqdCaZSooorsGFFFFSAtFJS0gCiiikAtFJS0gCiilHPFQAlL16V0Ok+DdS1LbJKv2WA/xSD5iPZf8cV6BoXg+zsmBtbYzTjrPLyR9Ow/CvPxOY0aPW7/rqS5pHAaT4O1LUdskq/ZYD/FIPmP0X/HFd/oXg+zsmBtbYzTjrPLyR9Ow/Cuzs9BRcNOfMb+6OB/9etyG0VFAVQqjoAK+bxeb1Kukdv6+85p4jsYdnoKLhpz5jf3RwP8A69bcNoqKAqhVHQAYq0qBe1Orx51JTfvM5pSctxqoFp1FFSSFFFFIApKWigBKKKKYBRRRQAUUUUAFFFFABikpaKAEopaSgAooooASgilopgRPCrVRutPinXEkYb0PcVpUYzTTtqgu1scdfaC2xhGBLGRgxuOo/ka4DWfANpcMz2ebOfuhGUP4dvw/KvbHiVqo3WnxTriSMN6HuK78LmNag7xZvCu1ufM2p6HqOkPi7t2CZwJV5Q/j/jWdX0XfaA2xhGBLGRgxuOo/ka4DWfAVncMz2ebOfuhGUP4dvw/Kvp8JnlOrpV0fdf5HVCrGWx5pRWhqmh6hpD4u7dgmcCReUP4/41n17cJxmuaLujQKKKKoYUtJRSAWiiipAKWkopALRRRSAKKK3tJ8Jalqm2Qp9ntz/wAtJRjI9h1P8qxqVYU1zTdkGxhVvaT4S1LVNshT7PAf+Wkoxkew6mu+0PwbZWLKYYDc3A/5ayDOD7DoP512dpoY4ac7j/dXpXiYvOYw0pf18jKdaMTjND8G2VkytDbm5uB/y1kGcH2HQfzrs7TQxw053H+6vStuC0SNQqqFUdgKtLGq9q+cr4yrWd2zjnXlLYqwWixqFVQqjsBVpY1Wn0hrlMQoopaAEooooAKKKKACiiigAooooGFFFFABRRRQAmKKWigBKKDRQAUUUUAFFFFACUUtFACUUtJTAuUUUViWFFFFABRRRQAUUUUAJRS0lAgooooAKSlooASiiimAUUUUAFFFFABRRRQAUUUUAGM1G8IapKKBGXeaXDcriSME9j0I/Gub1Dw6xjdAi3ELDBjcA5H06Gu4xmonhVq1p1p037rLjNx2PCtZ8AW0zO9gxtZu8T5KE/zH61w2o6Pf6TLsvLdowThX6q30PSvp680yG5XEkYJ7HuPxrnNQ8OsY3QItxC3BjcA5H06GvocHns4WjU1Xn/mdMK6e587UV6PrHgC2mZ3sGNrN3ifJQn+Y/WuG1LR7/SZdl5bsgJ4fqrfQ9K+lw+Oo4he69e3U6E0yjRRRXWMWikpevSkAUY5rotJ8G6lqW2SZfssB/ikHzEey/wCOK9B0HwdZ2TKbW2M046zy8kfTsPwrzcTmVChpe7/rqS5JHn+k+DdS1LbJMv2WA/xSD5iPZf8AHFegaF4Ps7JlNrbGacdZ5eSPp2H4V2lnoCLhpz5jf3R0/wDr1uQ2ixqAqhVHQAV8xjM4qVfdjt/X3nNOv2MKz0BFw058xv7o4H/163IbRUUBVCqOgAq0qBaWvHnUlP4mc0pOW41YwvanUUVJIUUUUAFFFFABRRRQAUUUUAFJS0UgEooopgFFFFABRRRQAUUUUAFFFFACUUtFACUUUUAFJS0UwEoxmiigRG8St2qjdadFOuJIw3oe4rSoxmmnbVAnY46+0BtjCMCWMjBjcdR/I1wGs+ArO4Zns82c/dCMofw7fh+Ve2PErVRutOinXEiBvQ9xXfhsxrUZXizohiGtz5n1PQ9Q0h8XduwTOBKvKH8f8azq+ir7QW2MEAljIwY3HUfyNcDrPgKzuGZ7PNnP3QjKH8O34flX02EzynU0q6ef/AOqFWMtjzOitHU9D1DSHxd27BM4Ei8ofxrOr241IzXNF3RqFLSUVQC0UVvaT4R1PVNshj+zW5/5aSjGR7Dqf5VjVrQpLmm7IRg1v6T4S1PVNshj+zQH/lpKMZHsOprvtD8GWViymG3NzcD/AJayjOD7DoP512tpoQ4ac7j/AHV6V4WLzuMNKX3/APAMp1oxOK0PwbZWTKYbc3NwP+Wsozg+w6D+ddpaaGOGnO4/3V6VuQWaRqFVQqjsBVlYwtfNV8bVrO7ZyTrylsVYLNY1CqoVR2Aq0sYWn0VymIUUUUgCiiigApMUtFACUUtJTAKKKKACiiigAooooAKKKKACiiigAooooGFFFFABSUtFACUUtJQAUUUUAFFFFAFuilpKxLCiiimAUUUUAFFFFABRRRQAlFLSUCCiiigApKWigBKKKKYBRRRQAUUUUAFFFFABRRRQAUUUUCEIzUbwhqlooGZV5pkNyuJIwT2PcfjXOah4dYxugRbiFhgxuAcj6dDXbkZqN4Q1a0606fwsqM5R2PCtY8AW0zO9gxtZu8T5KZ/mP1rhtR0e/wBKl2Xdu6ZOFccq30NfT15pkNyuJIwT2boR+NYFzoE8bfuGDqT0bgivoMHns4Llnr6/5nTCunueK6T4N1LUtsky/ZID/FIPmI9l/wAcV6DoPg6zsmU2lsZpx1nl5I+nYfhXaWegIuGuD5jf3R0/+vW5DaKihVUKo6ACufGZzVq+7Hb+vvJnX7GHZ6Ai4a4PmN/dHA/+vW5DaKihVUKo6ADFWlQLTq8WdSU9ZM55SctxqoFp1FFSSJRS0lABSUtFACUUUUxhRRRQIKKKKBBRRRQAUUUUAFGKKKQCUUtJTAKKKKACiiigAooooAKKKKACkpaKBCUUUUAFJS0lMYUUUUCCjGaKKAI3iDVRutOinXEiBvQ9xWlRjNNNrVAnbY46+0BtjBAJYyMGNx1H8jXA6z4CtLhmezzZz90Iyh/Dt+H5V7W8QaqN1p0U64kQN6HuK78NmNai7xZvCu1ufM+p6HqGkPi7t2CZwJF5Q/jV7SfCOp6ptkMf2a3P/LSUYyPYdT/KvbbrQ5UOYG3j+63Bqe00IcNcHcf7q9K9mefy9nolf+uh0PERtc4rQvBllYsphtzc3A/5ayjOD7DoP512tpoQ4ac7j/dXpW3BZrGoVVCqOwFWljC14NfG1a0ryZzTrylsVYLNY1CqoVR2Aq0sYWn0VybmIUUUUAFJS0UAJRS4pKACiiigAooooAKKOaKADFJ0paKYCUUUZoAKKKKACiiigAooooAKKKKACiiigYUlLRQAUYoooASilpMUAFFFFAFyiiisSxKKWkoAKKKKYBRRRQAUUUUAFFFFACUUtJQIKKKKACkpaKAEooopgFFFFABRRRQAUUUUAFFFFABRRRQIKSlooGJjNRtEpqSigBqoF7U6iigQUUUUAFFFFMAooooASilpKACiiigBKKKKYwooooJCiiigAooooAKKKKACiiigBKKWkoAKKKKACiiigAooooAKKKKACkpaKBCUUUUAJRS0lMAooooAKKKKACjrRRQAxolalWMLTqKACiiigAooooAKKKKACiiigAooooAKSlooASiiigAooooAKKKKACiiigBKKWjFMBKKKKACiiigAooooAKKKKACiiigAooooGFFFFABSUtFAFuiiisSwooooASilpKACiiimAUUUUAFFFFABRRRQAlFLSUCCiiigApKWigBKKKKYBRRRQAUUUUAFFFFABRRRQAUUUUCCkpaKBiUUUUAFFFFAgooooAKKKKYBRRRQAlFLSUAFFFFABSUtFACUUUUwCiiigQUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUUUUAFFFFABSUtFAhKKKKACkpaKAEopaSmAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUUUUAFFFFACUtFFACUUUUwCiiigAooooAKKKKACiiigYUUUUAW6KKKxNAooooEFFFFACUUtJQAUUUUwCiiigAooooAKKKKAEopaSgQUUUUAFJS0UAJRRRTAKKKKACiiigAooooAKKKKACiiigQUlLRQMSiiigAooooEFFFFABRRRTAKKKKAEopaSgAooooAKSlooASilpKACiiimIKKKKACiiigAooooASilpKACiiigAooooAKKKKACiiigApKWigQlFFFABRRRQAlFLRQAlFFFMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACjFFFACUUtJQAUUUUAFHFcZcePne61C10rQb2+k0+Ro7g71jUEMVG3qWyRwMZPpVK58ZeKjaGdPDUOnIFU79QnOGLMFVRwuCSe+MdTgVapy/porkkegUVyPgLxXdeKNPu5b6KCO4gmC7YVKjaRxkEk5yGrrs0pQcZOLE4tOzDFJS0UhCZopaTpQAUUUUAFFFFABRRRQAUUUUAW6KKKxNQooooAKKKKBBRRRQAlFLSUAFFFFMAooooAKKKKACiiigBKKWkoEFFFFABSUtFACUUUUwCiiigAooooAKKKKACiiigAooooEFJS0UDEooooAKKKKBBRRRQAUUUUwCiiigBKKWkoAKKKKACiiigBKKWkoAKKKKYgooooAKKKKACiiigBKKWkoAKKKKACiiigAooooAKKKKACkpaKBCUUUUAFFFFABRiiigBKKWkxQAUUUUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooASilooA8gaaWy+Jms6CuoC0s9VkAlcxhjuZQ4Ck/dY7ioPPUcZxjrPEkIbw1rsd/qct7DLMoEcHlxNbDcu1CWIB5xnPJB981jax4Wi1f4rMZLhI/9FjvRGQcyFTsC/T5RnHOOnXIo6l4t0/Sru/vP7Bhn1Q3kiiSWMN9mZI0U/Ptyw8zdjBHHpmuqSUnHk3sjZ6tWHfB+RYbnV7Vn+ZxG8eAcOELBiD7bk/MVaji1y/j8Satb+Ir22fTr+6SGEnfFtj+YAg8Y5x7ehqh4C8Q6prPjgTalP57NYtChQDamCrE4XgE7efc1003gC4kn1Bf+EivYbC/uZLie1gQLu3nkbsntgdOfSirK1RuVru3mOTtJ3Of17xnqV/oukCxvI7C7No1/dyFwgOzKqq567mB+X/dqxrl1e6kND1fT9dv7aDWLmGBreKT5IMgK2MdSCD+Oa6jTvBGlWdxeSTww3sc4iSOK4hV1hSNdqqM5ycdT3qCDwLBbQWdvHfS/Z7PU/t8EZXOwdfLBz0zzn3NZ88FsieaPQ5jxH4j1HRdYgtrbVWlt9FSI3YmlCyXjuwymP4vk56cc1saobzXvG1vYWOuXdnZPpK3iNavgOTIQD+II/Kte08FaWlpexahFFqEt3PLPJPNCu5S/Xaf4cY7d6zF8AXcDWUlp4iubee2tPsnmrCCWj3s4HXjGQP8AgIoUofMOaJiS+JNftGn8OHUVkvl1KCzTUfKBIjkB5x03DaOuep5yM1fvLvV/BWrW8cmrTarZ3VpdS+XdD5keGPfkN1weBjpya108AaaNFksHubt7iS4F218ZP33nDo4P4n8z35p2n+C1j1D7fq+p3OrXCxNDGJwFREYYb5R1JBIz703KH9LcOaJzun3d5HcaFf6v4pvBeanJG8dpBFmAqzDCEDgZBxntz1xmvTK4q3+HwtrqyVNavG0yyuluobKQBgrqcjDemSeMdz35rtqio037pM2nsV7p5I4t6PHGByzOCcD2HeqrXV0n2ZpFRfMKgpsPU9ct0HsKuT20dyqrKGIVtw2uV5/A0z7DBvVyHJGDzIxzjpnnn8azILFFFFMC3RSmkrE1CiiigAooooAKKKKBBRRRQAlFLSUAFFFFMAooooAKKKKACiiigBKKDRQIKKKKACkpaKAEooopgFFFFABRRRQAUUUUAFFFFABRRRQIKSloNAxKKKKACiiigQUUUUAFFFFMAooooASilpKACiiigAooooASig0UAFFFFAgooopgFFFFABRRRQAlFFFABRRRQAUUUUAFFFFABRRRQAUYoooEJRS0lABRRRQAUUUUAFJS0UAJRS0lMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAIp4VuIWicuFYYJjdkb8GUgj8DVKDQNJtSrR6fbmRc4ldA8hyckl2yxOSTknvWlRQAlFLSUAFFFFABRRRQAUUUUAFFFFACGilpKYBRScKowopkFtHaQrDEoVE+UAADpx0HA6DgcADBcD/2Q==\" />" + "<img src=\"data:image/jpeg;base64,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S43yEdOnQDJwPfvXJeMfgfpHiTU5dT069fS7qdi8yiLzI5GPJYDI2knrg49qHF2C559qnwo0ix1Gzs9Q+IVp9ru38uJGti5z2yRIdozxk4GTVT442Z07xPolk0nmtb6JBEZMY3FXkGce+K7bQf2e7CzvkuNa1dr6JGB+zQw+WrezNknHsMfWt74g/CNfHWu22pLrX2BYLVbYRC08zIDO2c7xj72MY7UWdguYPxo/5JB4e/6+rb/0nkrd+BP/ACTSL/r6m/mK2PGngD/hL/B+n6B/af2T7HLHJ5/keZv2RsmNu4Yzuz1NX/AnhH/hCvDSaP8Abvtu2V5PN8ry/vdsZP8AOmlqFzpaSloqxCUUtJQAUUUUAFFFFABRRRQAUlLRQAlGKKKYBSUtFACUUUUAFFFFAwpKWjmgBKKWkpgJRS0lABRRRQMSilopgJRRRQAUYoooASilpKACkpaKYCUUuKSgYlFLRQAlFFFABikpaKAEoxS4pKAEopaSmMKSiloASilpKYBRiiigBKKWkoGa9FFFchiFFFFACUUtJQMKKKKYhKKWkoAKKKKACiiigAooooAKjmijnheGaNZIpFKujDIYEYII7jFSUUAeSat+z74Xvrtp7K7vtPVjkwxsroPpuGR+ZrofB3wo8OeDLoXtok93fgELcXLAlAeDtAAA474z713VJSsMKSloqhCUtJS5oAKSiigAooooEJRS0UDG0UtJTAKKKKBBRRRQAUUYooGJRS0YoASiijFAgooooGFFFFABRiiigApKWigBKKXFJQB53pXwh0rSvGo8Txajevci4kuPJYLsy+7I6Zx81eiYoootYBKKWkpgFFFFABRRRQAUUUUAFFFFAxKKWjFADaKWimAlFFFABRRRQAUUUUAFFFFACUUUuKAEooopgJRS0YoASiiigYUUUUAFJS0UAJRiiimAlFLSUAFGKKKBiUUtGKYCUUUUAFJS0UAJRRiigApKWimAlFFFAxKKWigBKKKKAEIoxS0UDEpKWimIbS0tJigYYpKWjFACUUUUwNaiiiuQzCiiigQUUUUAJRS0lAwooopiEopaKAEooooAKKKKACiiigAooooASilpKACjFFFMQlFLRQMSilFJQAUUUUCCkpaKBjaKWkpiCiiigAooooAKKKKBhRRRQAlLRRQAUlBooAKKKKACiiigAo60UUAJRS0U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UUUAFFFFABijFFFMBKKWkxQAUUUUAFFFFACZpaKKBluiiisSwooopiCiiigAooooAKKKKACiiigAooooASilpKACiiigAooooAKSlpKACiiimIKKKKACiiigAooooASilpKAEwDTTED6U+igBoQCnUUUAFFFFABRRRQAUUUUAFJilooEJRS0lABRRRTAKKKKAEopaSgYUUUUAFFFFABRRRQIKKKKACilpKACkpaKAEooopgFFFFABRRRQAUUUUCCkpaKAEopTSUAFFFFABSEZpaKAIzED6ULGBUlJQAUUUUAFFFFABRRRQAUUUUAFFFFABSUtFMBKKXFJQAUUUUAFFFFIAooopgFFFFABRRRQAlFLRQAlFFFABRRRQAUUUUAJRS0UwEooooAKKKKALlJS0ViaCUUUUAFFFFMQUUUUAFFFFABRRRQAUUUUAFFFFACUUtJQAUUUUAFFFFABSUtJQAUUUUxBRRRQAUUUUAFFFFACUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRiiigQlFLRQMSiiimAUUUUCEopaSgAooooGFFFFAgooooAKKKKACiiigApKWigBKKKKACiiimAUUUUAFFFFAgooooASilpKACiiigAooooASilpKACiiigAooooAKKKKACiiigAooo70AFFFFACUUtFMBKKKKACiiigAooopAFFFFMAooooAKSlooASilooASiiigAooooAKKKKACkpaSmBcooorE0CkpaKAEooooAKKKKYgooooAKKKKACiiigAooooAKKKKAEopaSgAooooAKKKKACkpaSgAooopiCiiigAooooAKKKKAEopaSgAooooAKKKKACiiigAooooAKKKKACiiigQUlLRQMSiiimAUUUUhCUUtJTAKKKKACiiigAooooAKKKKACiiigAooooASilpKACiiimAUUUUAFFFFAgooooASilpKACiiigAooooASilpKACiiigAooooAKKKKACiiigAooooAKKKKACiiimAlFLRQAlFFFABRXP33jPRNPvprGW4le6hBLxRQO5UYyegx0rMj+JGnXTbbLTdUujgkGOBQCB7lq0VGbV0iuVnZ0VzfhTxfB4qF0YbWS3NuwBEhBJz/LpXSVEouL5Zbiaa0YUUUUhBRRRQAUUUUAJRS4pKACiiigAooooAt0UUVkaBRRRQAUlLRQAlFFFABRRRTEFFFFABRRRQAUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUlLSUAFFFFMQUUUUAFFFFABRRRQAlFLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUCCkpaKBiUUUUwCiiigQlFLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAHFJR3paAEopaSgAooopgFFJS0CCiiigBKKWkoAKKKKACiiigBKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkxS0UwPKNXvp9I+KN7bxSW8K6nFHGZZl4TKAAg/UV1usx3B0XVoLu7SW28lUijtod0q8DJIzye/GMVheL/AAzHrvj/AE1ZnaKB7U7nCn5irE7c9uDVLWtctbDULky6Kl7epKI03glFCrjOe4yM4+td1lNR5d7G29rEXwpla217WbOeTfK2DuzneVYgnPfrWxDf+J9SudeuLPVreKPTruWKO3ktwQ4XkAtnI44rG8Gaprup+K7STUI2MKRNGvlx7UUEEknHAOQP0rqZvA0j3OoGLXLyC1v5nlngjRRuLdRu+nFOs0qjcrar1CVr6mRqvjvUJtD0uXTPIivZoGurjzPuqiZGBn+8wwO/bvU2ra3rVx/Y1/pWsRw2erTRwJEbZWMJI5JOeeQeOK3bHwTpNrPcNLCt1FIkcUcU6BliRBgAZ9+Se5qKDwTa20VpBHdyiC01A30EeBhP+mY9s81l7SkvhQrxMTWvF2paNq0FuLmCe305Yv7TkKBWmZzjCrnqF+bAP8q0tSv9Z1Dxkuk6Vqsdnb/2eLsOYBKGO/b6+hFX4fBelG0u4b6Jb2S5mkmeaVBuDPwdp/hwAMYqivgWaGW1ntdeureeC0Fn5ixqS0YYsBz+A/Clz0um4XiZQ8X64yrow+zDVzqBs/tO0+XtAzvx6+1T3fiTWvCuoS2eq3EGpI9pJcQSrF5TblGdpAOMe9av/CCacNJFotxci5Fx9rF7v/e+d/ez0/D+vNOtfBdv9pnutVvrjU7iWA2+6YBQqHqAB0PvVc9HtoF4mZpes6qNU0oapr9qsl+izLYR2hxsYZAD54P1/Wu7rkrLwP8AZNQsJn1i7uLawbdbW8gU7OMAbupFdbWNVxbXKTK3QguZJY4w0SKxzyXbaqjGSSapnU5Ato7QKqz7AAZPmJJwcDHIA55xxVu8tFvIfKaR0XcGOzHzY7HIIIqN7ASFPMuZ3C7SykrhipyCQBxz6YzWRBbooooAt0UUVkaBRRRQAUUUUAFJS0GgBKKKKACiiimIKKKKACiiigAooooAKKKKACiiigBKKWkoAKKKKACiiigApKWkoAKKKKYgooooAKKKKACiiigBKKKKACiiigAooooAKKKKACiiigAooooAKKKKBBSUtJQMKKKKYBRRRQISilpDQAUUUUAFFFFABRRRQAUUUUAFFFFABSUtFACUopKKACiiigAoxRRTAKKKKACiiigQlFBooAKKKKACiiigBKKU0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA10WRCjDKkYIqC3sLS1ULDbxoBnGBz6nmrNFO7HcTFFLQelAhKKKKACiiigAooooAKKKKACiiigAoooNAH//2Q==\" />" ], "text/plain": [ "<IPython.core.display.HTML object>" @@ -176,7 +254,7 @@ " display(HTML(image_html))\n", "\n", "\n", - "file_path = \"/Users/rlm/Desktop/Eval_Sets/multi_modal_presentations/DDOG/img_23.jpg\"\n", + "file_path = \"../../../static/img/ollama_example_img.jpg\"\n", "pil_image = Image.open(file_path)\n", "image_b64 = convert_to_base64(pil_image)\n", "plt_img_base64(image_b64)" @@ -184,7 +262,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -193,7 +271,7 @@ "'90%'" ] }, - "execution_count": 5, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -220,7 +298,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.9.1" } }, "nbformat": 4, diff --git a/docs/static/img/ollama_example_img.jpg b/docs/static/img/ollama_example_img.jpg new file mode 100644 index 0000000000000000000000000000000000000000..86149b798443bec4ef8ef9a0852d92809658a77e GIT binary patch literal 65920 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 literal 0 HcmV?d00001 From 4b7969efc5d944c8de548608c29945a0687a377f Mon Sep 17 00:00:00 2001 From: Florian MOREL <90619575+florian-morel22@users.noreply.github.com> Date: Tue, 23 Jan 2024 07:07:03 +0100 Subject: [PATCH 178/215] community[minor]: New documents loader for visio files (with extension .vsdx) (#16171) **Description** : New documents loader for visio files (with extension .vsdx) A [visio file](https://fr.wikipedia.org/wiki/Microsoft_Visio) (with extension .vsdx) is associated with Microsoft Visio, a diagram creation software. It stores information about the structure, layout, and graphical elements of a diagram. This format facilitates the creation and sharing of visualizations in areas such as business, engineering, and computer science. A Visio file can contain multiple pages. Some of them may serve as the background for others, and this can occur across multiple layers. This loader extracts the textual content from each page and its associated pages, enabling the extraction of all visible text from each page, similar to what an OCR algorithm would do. **Dependencies** : xmltodict package --- .../document_loaders/example_data/fake.vsdx | Bin 0 -> 337190 bytes .../integrations/document_loaders/vsdx.ipynb | 486 ++++++++++++++++++ .../document_loaders/__init__.py | 2 + .../document_loaders/parsers/__init__.py | 2 + .../document_loaders/parsers/vsdx.py | 205 ++++++++ .../document_loaders/vsdx.py | 53 ++ libs/community/tests/examples/fake.vsdx | Bin 0 -> 337190 bytes .../parsers/test_public_api.py | 1 + .../parsers/test_vsdx_parser.py | 51 ++ .../document_loaders/test_imports.py | 1 + 10 files changed, 801 insertions(+) create mode 100644 docs/docs/integrations/document_loaders/example_data/fake.vsdx create mode 100644 docs/docs/integrations/document_loaders/vsdx.ipynb create mode 100644 libs/community/langchain_community/document_loaders/parsers/vsdx.py create mode 100644 libs/community/langchain_community/document_loaders/vsdx.py create mode 100644 libs/community/tests/examples/fake.vsdx create mode 100644 libs/community/tests/unit_tests/document_loaders/parsers/test_vsdx_parser.py diff --git a/docs/docs/integrations/document_loaders/example_data/fake.vsdx b/docs/docs/integrations/document_loaders/example_data/fake.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..4e6502942eaba0b96dcac5fb4084e528c7f6f6cf GIT binary patch literal 337190 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%0Dq=fIYOQ??ZKgnb8j zMk64yy=N(ZeDoB@`f_NoAhEn~UMO&UBm$?i;|{AxYOX~aYeT04ZAj76d4}aYr6v0V z3sN&$mG$jR6qTxjmFh527+cJ|sG94QYKB_$%dk!uRS%ocP#nmzUA66!f&GK4FBr`+ zHc^<SsTn0z$28iKy&(r0^mBj=T`L)?RZ<PpTvg=&8V;c0PYxR7r!t>oh`}dArXf&# zu8AIvR4El@F@M6z{1H{uO^!@;6VAj$LG*+wbEmv9^%|`4ISq>9!}VdJpn;t~f)28q z(x?PEApwKEB6#tD2bCpMX=nzOjQtj71^DX301C=gR4OEJJH*W;fD<Gk9%_YTF+97( zDIzN3Ta?fci{X=7;?`Iih>oBs7R!ZnF&M|KpcI<Xuq$qbe?^?phpLMdQ*s0J5?{<8 zi%augc^3UpzEVn3Jq8WvYTyIdhA>0XjQnT7R2K#V>Z&@saPR~MM<f(39l1Qj<tR~} zZwmkiP(csJs0D~bvsnWKYZ4unr{1VwEofL9EE;GaT8xAd?lQoVA?%g+M6l=$Y~RKp zk@e~*VHh?lD!e*$dz)~DW#4eizF8{zmIi}_rB#Yf4Uu$Q)m2MQ%`mi=^y<*gONNH) zF)YNaRt@3roV-`&eOxi)p;atXHEmUcAtSr=HJ;B%d|b2B87bV@Zo)%M#5CUgcTH|) zX@+An+ir6j8RPt6PJ{k?Xdxkigm>TVf@JwIwa5ADC)*x^M3sZMy}M4#A?#m0U-LV- z6*LZioAexLWLdL|i=PzQRSer$27|)nFJb56WYXxU8-f%|VT$ukp)eQ3r57jp!mP*W zw;AYjONOx$#n3&Pc`&va=smYov+Sxvk?{~yrQ&+n62hqzJva(zhJjEkmi2No59M>i zs+4TYs8}!wSSl7TJ!M2NVMM6dwr)akORZR|TqDv^{TmJYrSpTIgA90nY4CQf;UL~R zECW)g`&T=Y@{M`+^(qHHhvkRhT1sk>&OD7}%X(awZ1&f2Ci=!}eqt|=i|Ez=edOm5 z)5HtEu==MKb20)%67YS^%n^+(_FPI-_H*DI&wYq9$uvPaL?1UfdrYZ`X@U#^H1{D) z9M=&qs4ycCojyVWabk3~B4dV-AYR~!%CfmW)rEZ;FY#<dh$M*GbmTfTtHHMYCL0P^ z0&3uzqnH2Gi~-{S-3m5Ci1x710bJZW4pEvyW<o;=&LNZOf{%~Pt32jr9#}SOAhFUZ zx0a(JE6-X`Y5YSGCXi+R9Vsj>>qkiw56V3qcmq{_#pk^W&}5gq9$j{Q6h;MJH{x-l zWE_Av@@$QEnI+0nUPL{C7+5IyAm%c-1rZL;V0}mf05bAa$)&YMg!SQfVIxmgnndoA z03~e-FVdPl>EtjetY>~EoMG7&jhBh=%GoEDiD<5_d3ISZYL&8AG)kIZbkwR|H2tbu zf%j^~^!=B^8RoeSuSnI>HNB$i2E~?n3TGJSrmk98X=_*1vH_3E6;%#6<1krqn5>{D z>@F!DK33KdWAAmQOwcf{O4NkLBY9E~jo{$LC#H&Ae2l0W&R8;dc(Nkqjpz|C5CNZE zvj`XGgb`@uCFCHesCo6M6ICDw;0)p>UQAwedF&QAgAoz&VOQ}-OelypuQ4&gQfV}g zPzK44`~t{4z-<a{_$OXH)`boUH=1MQLJ~+e;~LVA`B>kWDsF|(66rdyt2uFlH%e5( z=!2(GVrn8OA@GU78F{oIkPHpBxs>x~$dw|y@rSa^{uG79WpPF_sxQdPfdg_t(Jn2i z@FPD*NR6g3Xa)o8cnN$Esz3-3p3?|$9(i0cuJEbQVIzr{>EJ01S~d*9sELC`2W7!a zSNMS3YQxW3oUtM9EVglu_gMOSa94019tL3xKqG6M!1#wDjrL-K8Dl-$GhqwcEMe^h zHge|$8&6;hU^`bc;4|s@z789PZU9@@c2W1LhK6YWSjh2$*g|+umdaRuqUx$@!@uNd zY*DV5D&SSAq8Sx!RgD9*I6#X7w4f(xKBWeWv;@Y2YszIu3=F!v7%XT=O+}I`9~lY6 zV9nA|NUnfz28;%4!R^AKlIY}Ohz}E8c9EofTHgh<fP*1B6Q&Yesso42_mQI{O8`w_ zCrMcXalS`=a;c>H!h{ifU66|+fdp=k00+5*vaU4j6P2I^B$TLr1O<>6_0bx%i>Tno zRggUNK}vhP5)zWy1P<6N0nQi-B{bZM6;wRQvz*c0jNpx^9p7QOZJ01bZ__c=DW#<b zlN{7wnK>H$P(aU9N!lu!+{()HEVSSNUIq>92Ehsr@dZ4<4v$g9Fv0|N#uAJU7(>)a zG<ys~+*3<67+;X_LadMFt+qK*8Mgv>AvOSoHwHlqR)(U&N@*Rm(Abu{I=Yye`a!@g zu`tC_`N0NUv5qGLDlmE8D~w08abeaEqG0zpqkyd_AX+f$ObaOQO<JE3gB5Y?5zckm zN34u#;eYie0eHhvhqud{l9^;ALc9L&{X=GC=Ug=G>!^7K#SYW9v4t2lA6^)$Su9r( zaLlNcu?=^%;uO_#sZzrd&6=g{vbNL$QaJhfr)0vr@H!j??`EF|KyFa977hk!?tSoS zmgIiUjG=!IJG1_V1(;WxmY1$xvze{30<%0FEkCW4dSE<Vi#tPFh6=YAy@HiQRt3JO z6!eTiSXR9yFOAaqF4YcR8-paYF7H8JYqi;K`8WD^hD%25pcD1;rWhJ~&(OqeEXTB0 z{WI}6I%`cP<Apf(gH}fb6>HDp@9`u)qc~$NEHG<_tr?x;(Q!W?wfX#h9FFLscHLje z6OSL*bEO{nQFaqwe{t_8t<jX9En@Vz886_Y6LsEq@2x|_>{ld42VYS?R~6zID&)wH ztLG_{UrB9BR7pEuN$Ci`AwF1gky4WufGc8Z)}(?(u#p}T*1%~*jm;fVp;#c5mB3Rw zXoy)Pvq;o-Mgqg2L@7FftQ8QBR<BT!E|#)Va1hFhBv#YLxsBgBaT|HJD0~iaxxUTb zlqg5mC^6k;(CI1H!A46x>Q3rShL^`j(UN1LL_3P(OiaZ^)-pg|x<3?kFOo>>r^TWo zI+2oeXC>-dRJtMNEf8yL@T!d`i`JyG-qf{{A%jQvUhP9OL?a4y+3+t;{ztQFuBKux zY|GU?o?%xly;3V`2HbcNz{4q84lpDfbu``aT-DONmzzK<ookwnSZ_&U=c#x!CNw66 zodt8r(xsG%QJVt3U&VlNu0D<T(n<%E_#>|%&iN7H74NUX=f!x^5#ZwQ2t3rCz-E2+ zOXF;gJ<`sw#yFUCTWxGwG#Ssvv(FE~*$^wO=MQIW_-+em7XsT&&${C2aP~{}*E8(r z5%xxBmk=}^cV~EN1QBwaguOna#aTF-2ZZCkb7$i%MxIS9`;JO0hGDpvELMuc6x0e8 z^i@={=On!T)$tLy+K+D@uVn7jLL_kuo|u;{EUDEfs*%{4B8ge<$Uc=yCq?{(N7h2{ zV69S8hm}rs<yzuGk_kBGjr&cyu6A_A)OU$Ik}T?{M<2Zq*H5V{EnRPfn^DRo&io~r zL|>MfuZR4~JG+8!OA?FQ2w%iPVA}Li$pROizaz^UV;Gl1q#YkglXp#;y5ktOJeemr zcv%tGAPmF%KduKdJHnR@5ok3%fiJH^vU`0VwT6Tv)}xqBd>Js~1%(00oWim;o`&>l zI|>RdY=|_(gAr{E;eRByIKoy**kE!z8R9G6Rx*UYbN~T6Ski!k$l3arhYrHyvVlqh zMZ6iLkG+ckim=x=9jvNBXXO%N?&~F!mmRsSsQ^(mcp#M=!z@;H$1kd?k0sDmqsIKG zVArrS<PM<7gcr}ZHKfUJ`8gDniA9#;mjv`279zc&y}fz?1xE4bn54#7Eq2o(^XtWl z`}XZ?xqttdPRF|%i0EHezN&+5h;6xtm0Z$G@cyT(N2R^x9mMV3b!!}Y_Llb?w_@3P zwk0%MVtWqgx73KUW+fK@6uqRa+**?2ppuglx#SGS^9`1v6~!4Sg}snwfJX7=+}N0# z!IHd7H*Erd_e#t|h#3A;?5o=jyCK5(V^8fu*#7c=@x=}8fT%DU%7`j!W`Z}fRq^yP zAdy85RZk%*M0fKM<;^Xc8p78Z*nk2-=zO(UGd#_P*IK!(?t-YaZ;Pj_bCZYu#v6Hv z%mFJb2Pb%t<yir+@>V5jLMG*2Z%&ffd4x)48&@^IG`F~kNuEi}7(8Sts~1om)_><F z8{dpaJedej1hyuk-?5)Ix=H>4EzcHW(!2#*(piK5CC}c>fkUaru4qowC$ED!X29@# zkemH+pxtl?j|+MTEnnYZ=5KkNN+|!HVKHfL$CI_%C3Amzx@iz?y%c{U(;1qJDfBIg zt4s96bR<bgXf!_=^JcY2@PAPv@q#0goaJR1Sw3O_Z^|cWmlF3s`wqlyG@U(xLxBwI zWDbM>T8|8S>=Mru%<L^_k`;6uc>By#o~mUF+!JC&@oFf$cIIz==wK|xPte_K&o5k! zN8!e$bj(1THi0{9UE2iprl<h8_kb}%+NbI9+4pHuv2vdm;Q3nsyQ)#Hl&WMZLv%od z7{-!++lQ&l^{WQ(o~~6&?hdB1Zw&0VaUaXUyltwaI4&`(Js0ZR%~>n~i@Q}jAj5Cx zW47O}m3T0m#ZQT|Nvr+a8Sk<b??-v|<@bNg!v{8-pZ$6UBl?%$Ct)x+YY)cZbj+5$ z!)#$RtkSTYLIpwOMcp)RWRjppJ6I+qw2KdKW0O$C3Al6ha}1c;S)veu_p4mqHEDUE zB<7`|i~vT)`A96<A|uNF639I~sh=4bLEcJ-j08z(F&`%p#OaEwST7-?L9E@566iWs zrRP9U%lE;cTV@cBIhB^)A*ulXf-Xrhv0yKkhjtuS;4N{89W8SlJCys_>yzZLpOth+ z)<T~3@rx1rCV@OnFN<Md6p8Q<CRTD~e@J{&#rD+%ZMNB!yXv}FEm*-^q3s}Ip=TRK zyXMhUV69@f*s|Mq%>%w!+P!H59Lid?b^|I0d~-tBbG4C<QR%ZsIoc23G=m9X$_R)B zPF3NEx3Go76yBTgT7)gRahPIbTlfh`JKCPz%_l)?F2S96Gw%kuUyTmCP>R9Uk4+ik zJHkHfOhS);fgbn_=K*%L4jbYaPsF&&+xpvj;D`0&qrQI629St9`JyFdsf)8~^r!^; zEs9ZGumvIl4)4v)l-yv8aT24rCzzunc3NaC(w6s?thb>(80zxSWy91XB)Gg|6SYmL z16Ognl8(rVqrO@~rhbep_LmT~C^Vpp-9d+ieAj4qOS*=BVwMtyD61LrMvPHsMPI(b zyD`c{^p~}W?`K?qt3?vI+^vXN_WfpYLVnZ<_~J!{Vxvep<&7bu?IUsLPw_*vnmys? z&Z>$%CJER<T#m9!o*@#qPe1ye&y%2RBT99{*;?GQJ_Iwyu>|+L4<<uAx^f$B%jjn{ z#~%{|RW#nOa+|%l4NUgwHKPpVL%|$v4|{Q2*u~q@9qcPrb9Kvm!C;Qt7;FwUo_7Zu z&r92Q;-|wI9LPTs>=}%E<O`V?d8VJIvoHUGm-%e`FvhCmhkl6j2^;{XV(s83S}u6l zew)WE8h{weWDP4QV^AWtW@gaB1FO^Q8J^Q?yscx1H3G4|2yjT$?i<HP_r_`So;Rb$ zo3{~wuWtzMyn%?@&mPcbh7gUm9yYFiDy4H>f|JL`I%-NEY>(*>;3AkHCyN0WK^-|U zz^dm4=Y>>10X_f?`$<Teqxy8QJL`~<kf|SOx9KY6zQN|Cd<`r65i^CyM>5b_HVr;? ze{$t~XQjSn>`^Bsv8Zqr1_Jvj{}i#fjmy)$C?GCJ`KrA5-6ee+ovZ~u>tj(PXkh|; zn!dB%;^Vw&W()s#aL2?3ldjJuAI3aScz>5X2Z1EZ74i|sloldLVm8b1iirJW)=HJK z@7OOG_vrw4wu-iJfIBY@cODXx&uBiR=_-Och<gyi&@-CB_;VKyg0oII6)Qx#SW`!y zjcf#(o=pPKN7L3Fcre*D`11Qhe>@5#^AQ<}9>(pyOj}vp4hDlc#7u{+$&96tx73e2 zT_${0=&d?QJPsIq6m%XZJm3Zd?`ha55qOCz&@`8#doVmG5gG!qaatGSYX|0`SgZ#x znt)IW%toC>I{vnsJzj{7_7?4!x$ZoM4z&o(!NVmLxQlz3Erfw8-L$lgHB;;WLfn!B z8g(HLR9ON^6jVt9x5HG_7lR_2i!c$<^Ad!Nyadq$;1@~MXM6&hbQPonrGyjwDI$`n z5-N+Sq@A0ZZWzmm=Guwr$BPueSDo=8=jCf)>j_bhj~en-#0I6V)Q_pOo^%b#FF;A_ z_pog84NOub#i_>{J5Jb$FqILqCR-o)R?vN9V-Ze(N;$GN?=TLG1W7yO=~is40plXd zg&~UH132<F2d}!MC=BkST%T`6BoVkSGCr<FPwRtjs}>xJBmeJT9tt<%-5n<B3yrBa zpJEMMRFce^-OTS=qryi#FtHl0(8oHtEN>oCO@hQ9TyhdZ9k@!;i$3!^11vPAmM%`P zzr|H+*6zEXMSi1ew~AnrCEKJ30@$AGY1eMkD3!~4)hcQw9s7lvnqG9UH79oWsvvl# zgK*s53(k-oT)P+WQRcu&t7nZ4uHE$CB6;2KH^DUxhY!FQ_&*+nU;Z`OX8nGhhhl$B zZ}v6(IUXJOJ>R*eGpFjf{7pn@T!?7txF!hnSQn3WdLd?6{0*}QN_q{HUHM{3H9h(` z2o0XV3lUyL!J{5$7&wJrNHU<rt?l}qjJms4T+RP-;<zUzrFZ~tx`u3oAOj8cSQmgs zPCa_lZ&+-SO6UgdLg_0448>G<vo5F+t}hvAqSkW~ggRLr)(^!m*LOnC&By1^PC$&f zZ+H48OHHp!D)WtocKomKmMFp_=L4Pce{b|ZgnJ|`jZZfu-C2owEyP$KZ(Fr03B(Xf zyL3YkE>3t|$vNel(O%+MMJ9oU&5OwPUn52fsMAnHyz0JMBD;&W&8mA#v*1h&%uzF3 z014Ns6>ZfpidA2$xK%{2wEY)cBc`6`eOhQWST@vSGaHa-n<`tl5-+!tR$9M~Y`GR` z$3P&8f3!;!%5UI!!~XPf=%(*|$yo+5?CW?#OUB-e3pH3h=7W}uL89(?)>xMN;T(Rn zBr{e4Ko5c-Q8W*P!JGwWG9etn(lZ#aLFWgsxJZxdeNI<D`1g^-bq?b8?z-<`iTwT- zm#@h`l&=Q!gm^eT2Uv?`r{8N<-0SOOgsT=}FGqG2Y;!j11f9YS0&=$~RCvL~YZ+F< z!{D;c1VbGB;)bY12tE{7{PIt%vBiU|K+$~>?0fqE{+Sc<Z??^p;CTa29GGRc@!auj zPlGkbD0-%aElg~uR<tw|h|F@;s^<D7!+1f@or7)WV4IN)FI#|~{V>bHHj_SC#o52- zDV}e2+x+(0@ib3n#Dfhb|L)HtAm|xB&R|$vmVil|yaX`oN&O7+!lX&oGQ)D)u=!-| zKtjT*4%q%;SJhskeFx%J^e*?>76*2=VcQIwUvRPLYTQIRh*>MP&0MB=!MufNuw?FR zQe~ZOMqeKqTD8*EQYi@W@G>->&o-fu*I$0`VcQYfd!!)3{NOx6BQJ|RO0I2It77N0 zZB~ZcZdFyaQWfT$DppR}j!otqr|1H7s2<`@yI%F>R!$wT#{ql%8?gucSNI<rel5|f z#TFKk*RU&NI6F)bQ8zr9o*wxy0k)j#Yo<+Bij${&#CbpRUP*=vK3RqkJhr4DLShGm z_<9hsyM&AexY)0h5J$*izmPbF4zBlGq?NZ~f59wnI}pOVNMJi#B(A{!kYf6W+GofC zaSTH031z#y04dUK5T`qWko5tc@!p?UL~5A*Spa@u&x4psL2xHQu^Y^@Mv?988RrNm zNDh6ILhEDDCa;w{KpR@UIT?quC|u1HetTk1G%~(mD(Ybpe)%2VeA6g~O}87ue{WJ4 z&qXZg357AlR%@ftm*0cwH=T!yqCXp3IJCH0xuzLax2PKkQ)n19%tKYmM$y%LY>8nS z701+G4!CSHsdSi!g3D(Cah~I8CfOS}6CgWgp!uzXe@xP0n<k$2v%$m<n`G(;F-j-l zLqB#5&Stj&NA$G)BZH%*2m5Y>)x*GUG9>PUIH@ysJpKRdUE6ZvND_VTi2esXt#<=x zT|rXpxFd7{L&Wwpz1=Yr({C7qj29a(m%;A+q4|&bu|1hpx?&h%yM3W0M+hlZRVtNm z<smEc<c~LYhq7F>ouFBFrSmO(INC_|N@NlQjuMyLf)L>4@_KoEl=q{_INwUhGtT+h zT&xyxN((F`wJc@H!JJA)K3Lsgh3OJ85D1D`D3ahpLGkI|29W-9FcIv|%9M&kz(*GY zusSc^DfjmGy~9mM!lN>4N@mAz`dSl#;4M8wnpt@My2yvoaC|uG=pC(Th7~+YQkH%k zj~AomD{|AR;1Qr;F30SH+;r}Tmg3o`LQCaz^^d2b%Z*E!Qq|a%8Kt|+@Yo#+TG~N) z>>`3lI@f$A@``!hW&{wG04Kh_Dik<uqf)6TDc1;0&-NR7ixOa%Z4FUUx&uZ*WXAAp zx1$AmWmF=Y&8u{7u29npKuvP$sX)`+%Bm=>6n}9<%AZ$bSAMLDB@YTT?VwC{QwAM) zB!jT@oh~KEM=V$g$zR%*Uf*IHvOvH?xr~=k6BNop2QJB$t=aGrYBrHSq7#67C)EAG zGfZ96o)Z@}ZOhS+z07TCEzm}1gLF1l*>;poLCw~T5t{*9X{E}3^gP+;A0^D|Dcp?Y zd~#1pL1}faR^2MKUR2iQ&#SR3KUT$(2L*|CuqnGTL>&gBFOwyC4NZ}STLClIbQi7i z?xH#45*#`rWjRv@XJ^VlS>9eFz4ugEW?Fz>wYVWNYk{LoS-?4D@tA9ABIF%wV}H$^ zV_8ib@mR#Q16xohflO30-kuNr*zZ8$qv<KE;RL8VL*C1<6Q;0c3l<W|So8NuD{aF< zch#yw-d|X9`)627<B6uu6Ae)SPN#L6BkGtS>T-Ih^F)9Ec1#3ypJt>*9pRueB%~Of zfuV2!F6PTc{cJW|QKWkk(7tchR!_7E66Zzscf_IOYu)vDU*-cmwDQaG5u6V;Xb;qX z&(>gMl;ip8xfzSveb4g>NbNabnc~&hWd>u<!-E2$+nKFFQ<yAf@?@`k216dOsD}{$ z_4b9$&h*be;H?h4NK9|UK1(_!Up@y#xB80R8*E~cHOsGLSYcaln*fjCTqL+ijMc=n z)Y4#6Gqt8qj#&O+G3tfKNB?V@O~co1yAk*%>}{rLH{77zY1nQH3zMPOc06kju<7Wo zef0h(hFIsL(Ng)><ZASJc{?46Nq#b&re|M9H_PPg{ZBCs5LNzla<)8Y)lkLcRWW%_ zVJy=_U_GX59E<<y`ZYK{mi#{*OTeUhh1iwRTx8zB5fXN=kdttL$9guLFcKu`#q4TG z$;;~TK?zgZ$)}g?z)rn;!1g_YwjR)(Le&TMF3lb<c#tW_1i1liYFtE&-4R*0Nm78C z>!f}T#c*(0q5DDMeD~=CAGX-|D`>7<bl>LNiY3hae1&&M9IH$^P^5q4bH0tsm9mNI z;#ThX%)Qy<`c{g8?F`d~mYUMM`4DVJq<)ofwgO{*Stvsc(U|o_ox%LSD}>$c1~g0f zpGVWl0x67?JH6kt1*Rwry^qy}%LzyQNrr0*|HFI>fIy*u(R_lKKNMxcgN5>U^dAM6 z$^?8%2r*RP?G6`tOvg0y{^R~pfd%xW8-B+_rh1s(^b+XFbitzp?t+G9Sa9I7+sJ>` zHaxIiyRPROb~_CAaeJ@OQvw2Ej!AG2<3M^*$49-NvqqA*P&Sf}*}!5b85gJ``EsF; zj~~e==ZSiSl!O+4u>0@J(5FeRiA3EC$;;?n(y78h)w{xg>~YFtR}LxtPEh5Pc9#Tn zzRj_7MszN44`HCUZ)_5{_)}{j%%Yt-uAzW)2QH?fJGVWN(srV3L&M6DbY5>G0}yNt z+ejdhp%Kjexyw&#(P;xpI4#E^Oj5?FJT&1r-gJnmz|$M>BR3kJ<%8|9)3TZf%ClSc zJ{BFLGUw!20|-h~fFuoNe4)AJKi^)S&X9WpS>=&0zCM_rVn3e3V8hF7hnwhY_goKi z0=7*B8K%`TriE@SWlo#8Of2P3x+0sCa#5f2S5oy}+y*}Tu{2E=MsSIKQqxtLNnSR` zX`mnuGik`kYQklaN=()grWF1;PDT~JiXAkJkfC>AT@=p^c~TE$ow#rhgiY9noM$8R z{E*3f9A;cJeptce=*W8URiV82jI(+2QCGD-aN39u+<sh^J|oc_8-r3gr<mx9<Pky| zctKA*FR5mco)uSm;<>maI42?Du?ytAsqazp<T0gHklrYht}H7|M*z*mZ`=EjEpoqO z?wte|H6-9tYY|>l%~a1-wR=_Tt5;3cR=+u8wQqr2=|&x|RcDN}Y_;&48mVg+IXJA7 z@!GHXSHKT%-$Y+kWJviqUXR=U)h$Nj>kZ!OiG}C(|KqDxZ;0<)xBtmkFS?d@=B#U< zyfGRI`VPK}A?|?=+^FgWvfH7#Tl&CPC<{-vnPIH$wA1+YpMZ;CG9QA+e~uNG@@&&X zQ3?D^&&=-FX&aVa-ftTzIYLc`qqZyjwmqxiI7Yi+bee6m1#W4pS!MmQY$KQTBJYm0 z%gj+hkHkF0o?kB%di-$A(pdONw2V{?-3j!DZwb8`>rBsc*OZ-V=#3Jsg0zv|i1nXp z`ch*<>vlg18K&c5tkZix3-u}~c_V3@I5(x3KO)gaPih%?N!%~Wvoc$(XM17sM<qW^ zp)4|VL;n_96`D9G{E(&QXgs29x9He}UhRsX#_6RR5@`ClC!lNRz#FoDws+LRMju?1 zE%rfG1H6bKX|v2m&p;|TO2$@P<hR$8NJp0E=iRs)wMy?b8C~w3YhrA$1=+rtBbJdg zY1s-AG>CUC-UdEJZ7Po5^`bR@pE6RqGD33E5xzNlZJT(5bUQ!?5EB~e6NU^KL-pxv z<MTHy&GIF3e4WrOImZ{8`;AUMCX$VF?jV0Lfj6)*;1zeEL&my3r+X*7vF63Sl6Ye~ zv$UZ@Ye-`TtQD7&D|k0!F{VsNKDn%5L7VEVZti2{kQ_%#cALJQt>(($f&<%>vlUvt zS&klSamFxB!!wb69ZoG}3?Xe~4$~ZRp>|quZ3z%(@6j%_gO=wQe&FhzJzQHJ*$`63 zDVm@`cQlF4muzt;QzdnY-r`4{!yakrZ4T7A;?$pKOcqux?G)qbpTaRFo>A&Nqrl;1 zI*T}My9^MkFK!phKmWKyO7_|H3|`UK=Mx+=saeK3W{kw{%Mu<i;wSAv^IMBt?8u&t z{MpHts8miX5sOppO-UuBG^4)Xz5%N=)X3&0#<3ueO)NQa$odF6&OA#vnh6rb`QLMW zz`faQlIerswI*r)8AR@cD0eTMaLuA37d4AT`OfLnu{w`ih(AT|L?0+yd9e?|40P8s z#LdNbMa}eHu@Iw>9Hs|}AuAOTcA*rmwB`H2;{|E14_pqr+&IQU`YiS?d+i>_++57P zWprIjt}be3W`>xVnc0pRV`gS%W@e6=A!cUg*p8W*8DoaKcJ_Jw`gZsE@y0lB-1}pB zdDc>uq)(c2%u<z9C3@@Ff+(MEAVPW;$#TQazG6o`$TC$9H{sLJlUYcaH-+>%ey^du zFVJoRUtW{M6pn-Q5@>~th^-WiFj|(2F{awYx=DQQ${^=ElrNQBTNdu?v$s~m3r}`v zkbYKEbVx?L95ozRONLyI$lMo=j<6!b>aEg&g9&GDu;SqG`r@XBE)Y_{>4z~{PuugZ zSz8!{c<Xbl%MPT^RHNT|7%&-SxRGRg6Ks`}@d#P(A>wH31u^+C(*;-${_y!VYDzeS z2E(voJl)R0gf2-U-zljqlQqOK{@tnQK~4*)LezZAVKKTNvo)kZnD(A{O}HC!9<Rk; zEczV6M_fxS*k~rKF+}RKKk@evOZaA0*Y9{%(&l?A%Pvhn85~rtRgf)zFIWdxQjG_` zT2QxbMc1uDw;{|7AK>{DrWon8%^=p>Yc8V5qdWhoXnoi$(3RRE!Wl8@!(2lO&G_;S zhgHR;w^MNc!Y0}uToHUC?N6#U-dBU)*Age@nLPlrAN5}Hpm+&~TK7*sKqmO-Cn%7D zG$<Gv5Cjku;F=idxvg*?1{eqk3KR$kAJF1z;bdXQpyy~}?ZjYgXXIjIV(a`@syO{$ zEuD#zmII7P!B-*OLCaorYw!8VqixB;)nq<E#8ozmXOL2|nJrg|?WFY83;{WV)z{uE z1XYW<uKGODW9}4r)L-?Flb42FKsK8hIO2MhQspN4c+l*7!Ojj}EPqD^{k&H^m6eQE z(MI`ogqOo9Um-!GyaUSJZsF6&Jcq?YP~@40f7?z==+3+3Z(1<Mt|_>FtfjvuLUhYu zW2JIG*3zc1d7GUNM5>vhtk}&V_v-Tn8Jp)P$gr4DxBoQB*+hK6Z8mTm$wA2hN@F|R z*B&0_sM4&xz_=rnmk-2y8J}*On0~Vwe%f~4xi8Zyd^y&5BUdI}`#YC@yea|&Py6re zdjAo`#Y>>WC>S6hL?0j^^nZY8Z(wHP#PH|upJaO-Eyp!ZjQ6XGP0$yL^YUs1FhZgX z^~?HfS54K(?E2)QSF6Z;q6l>UdGF;LT75M}=?wgza;9WX!2Dp5$+R>VuDe3igc#8N zq)_W#mu-jy?{8c%Gi<UAL=j;yYHZ*$%kri|omkXj{C<~|$47ftJv^RgbmpOXE?Mv? zCOI{_a1a?iMG0x;irb}9sXv_-<O_tDb0PWhwILY~1($01!#j!8#=8NVy047IjIi8t z0JLV7iS!l3x@5k{Gn`>Utjv-Y#|QRlE6nO9-i??m<98<t8pW)ICv2**&pHJ&H0~(w z@vsa9SE1u4R^T!94PPdb%D96kk~W-vDGHV5d|D)si}CIWY(d_e?s^exLsy386S;1~ zT}GAkz@y8uMOYy3W`-VeW|HN6_>`qrh|>l}z)t(?Xm9KqOIgGY3&~JC)8S%WKJY>f z{H~#T&*JEbjhiyZDu~MO-|P?5Pi5F)w&zg~3DB{|iaj_UY(@v0em#s~Hh+9dfrv7h z>fofd3X3pNFEKFp;Uk||nLO~$nR(;b6jNq$Wn6R)I&L>zZC`Q9a4FsLC_VD}Z5TID z+T0#*+8SDA(QNb`r`Z}6L+>a+w|Eo&$COA1_-EeS8u|fd<6s2Jh>fPXVU%Jg96!V} z>4VzAdnCn5VKnOJ9J)?OEp;Wt`GY(I!-x-^9(pC?b(i<IHMhdY+8BNagZM4inlbXO zPqpW>CoBj*w0lzB;NN7yE`6HUT)dBOyi(8G>YbC8{MF{<avAjOJJT{p&$tXawfG3q zO3EQErP#~PQqw%f_%+L!yk$qM8u;^9tYbo8>m6q%KW$rhgzV;srdG-)Sb;2hLs0FS zm?UU2<r2SCErQw7wI^BOU^;`aXkW6$^Jjzi*ecsrb+ezdlS|u)QPh@<XK#9Dm`SA< zp^{ghQ0B}`zwu8l0_rdDm-=>hOlqKv{5r1ApGta)4Jh$(W2Vdn5#76)L5Ej-`2qx; z6>IvY_@trO|2+jQ3>tUOOt;aqyl`&mn2VcfxPz2mSb%s-i%AEBBs?=;{)NS)_Y!t0 zARfa-=($o6Ow`5t+eoZ!2R*w`ZS?}!F|f!2>x45L?)Ow%E_qQz>(L}7E9-AS!cHF* z8A=B<$VJ2KW7DbqZ}%9o(g%<X(H3s*kBrOy<wH#ssANx|a5R8b9deACHHO&YLo8}s zCFI89c|5&5uIj`u3^OEpr*sxQi&#pPc(Q^90cB>#9_JggXrfoDjzQvZ9Rpiz^`@!7 zxntrZ5t3nN^1hlanqGb&o;^TL!9s*}pkX#C&Q@Kxu{fT(*z2U~jM)xYMdt)IMCMk< zO9nHb%B^FlkV}A4|A-v2UFz<!z(0|pmMw3$Yi!yx?9O6s$^BobwXCeqi&OkwKvaK} zw}Zz~WSZ!^Su_VQ^;HX5*ODxnwXu^g6RA5nhqZ>P48!qV7Dj`E(^Gdj2G}m`@NE<^ z)4_XMgkD&&@E2i|r%);L+IZD2i6)JsQ4WEoSj2E+0oHx+jq;F0--v)m*n17^C*rcv z$AG*hA02yV#8Cy2Rg8fPy=TH^?!P&^dN92o7_Qp7LxT3|$4CN~{SF8PuT|M#)ERNn znVbjGY;=HBv^m!Vo6^K+^!n0UD6Qe}lAt`gHs)_z)5R4)!0SNd$psWDGpApdN7X=4 zO{u-e&|)bd4?K~0gQlAMyE`q|pA;-um9~7}=Er@2O1;1GW`G?M7~NcVtx4f|Cg}GG z=V8R9$fvGvxYKkjA2ykym5K6@h#LxRlE1i~sG@%Dy?PEvVpLO?hk7Z&^UTbQeopro z*$cdGOWgF{Wb(<Dp^5b>$9P{mxaHKO<&+=Eq=hFww|E|zBf?DVd$k2o>Lm2vD*2T& ze>o!`O>26EdSagOAuW*M%UP%zr}z6r{pfoM8zKziMPcxQG&LD@=P-3qSz`{bL^jk| zUibe|Vws*Q?tH%5z9%`l8dLrBlF{%gq?Tp}39X@#91-p7F1afEQZBf>*!dlZ;8S?C zeW7NcOB=r@79m>vU?~Glalg35Mu)VbcM~F}-tZK|cODD|v*5a8q719Tq5}!h)T;fU zKD;)SVnnIVVl|kPzV9I^qVm|0WQ(+Lmbq9oEFn4(iy9E>Gfz(yNxMiWB_tyrLzMEJ zXN`9DvPa1Z31bn~2oE6b5~mhTUV3?QNjF}R3t8XVm5W)2_MoV+;6`v|3+IKT7zq_8 zM*{xA6^zwBwo6kKSBM(>`5QO-w`;O3%t=YSZDd%KyC2?n`PK?&<@Gt)I>>D6M@!2B zj!e%%rI9b(utblLv*=T>P~f;{P|V$w`=&p)f1&kbi$#88trQ5=eS@waQPan3)UvU3 zOeG>_?6C*6n!_&8i-;tykz;|LiGYsO<b)+o)K~AoX|6j+Z#UIaijiiUnp6yzNR6;! zc92zSAz~m*YfrI!yH@|nH{Uu}Gc3fy5dm-Wn}+&i?ndglXTw-o#O}pZ4F4xvW3?77 z6M_=Nq~`au{7~Me^;+6ggEN#7dkr|j0sPT<#~dmKcpLg6A|Rl}LsOh~8M>?TWFqAW zgW~&OPVckwgTU%H*zC%F)#7{~d6AA-o3qK(_#6MQ;r5h1J~&FK6;y?$JRvq~FJmZS zBv2xXTd*n^N|N2e21Y7VY#7622C8u)4UA%EV2>sne-gtyL}>;If+MP5hysmkF8&G! zV&?roo>>Sup|{Yq5b!*UC3AhjgD`MoS=`Bub+vSL@y0ZyT768lv<;w&pNUoR*5Jl~ z<(K-WqDF*V?-Ua(QVecsW81;P<VXL4Z?pjppp1rQ*|r_fSX@e?0_6Uvb1)<yRq!98 z@mN7imBHbgc%6n&mR!1!Qhi9r_?vWIgriXQ$lB`72;hiXmo<s}BqfC%skYxCfb^wu zoAyc6q4WfNTx$j))Elg+CI%rgmV23nObkVoLkjXuUBCGyx3x}2^Ig>Iq1b)x<WeDk zv~zT_MzuS2$3N2Eku9TFVoE04M!Pw5ZC#P%9z?R~*m&oX(h4zLMgo?_n+yQcobj3f zHr3!*tIZ>#O(^d1TK@{EX2Vsx20Wx|OmyF_*-pE?TIQEgPpX`1IdkooSx?GTFc=;e zg8XWQz+*+h9u_>^|8~$I_EA3wh-%|Z20m+x*>w^b6}vWzuY%5IVG_Sw34DfZPsf|C z5C}cuYt~QM+kFK?S!jYEsbziA_OEiYkQxbH=gtv?JaI`(eS_dzc`PKG5LA|u{;H`o zn^+Zz98*VcA@wAfwB4B+-%vNGPRsAMHxb{v8Q+Uj@|X%0EFAf)to^NPdVz<}Yd>In zGjOgQ!C5Z_in(~kk|&ZEx-X>*p7T<2x=|Gr^{F}+)CI%Z3zb|r+v2GgeBUhd+Jyh< zH1A$F3N|C5!^@+_tCGFJZGv~`XTM&Wb@l0Q-uYU{_@u{xjNdG5j16-clPhA#eP2(X zx7A9$CVfX?kn4qm{bfj*s3T@HStXOH6)R|sH((eJ{cm6e2nYDTsoB*Hzi>tD6}^Gj zxDB^?+}rpy+b-?lN1qMVZ}Dyu@q7^e$G2C>c2iYYz!abo0tg6jd;HfF;P1EBzb69! zc!&Kn3;3~6XB)(bG<?&gv<19D@G~KpR8^)~Qls)7nARQ$+}c=6@$R9EUbVd9yKN)V zR@|pyZ0WScPXhMAJ|6gV%y^BZCs+II(6}>?rZ8IaJxvx*Gd5nEVCDB0NAH$Gse7gK zFfP-^-GWg5;%C|N<$G6VrpE@C`~AZKV-$RmQq#G0Su<4)v=o>gjyzp!(=;`a*2aCk zT$VAv=F)s<{uixrT!HctL15O%A;;v-so6m2Y+0Oc`PVFd==o2a>S8W!ryR}^OP-Tf z{H4%h)-bZ82&W&RyAIk~WL2tJp|=jB9$F{ubfv_A#Kvzh7+Ea|hwNFd@rwvq0HP=5 zQjDJT7uf$am&$c@|Jn%v5)usvi1Z&o{y%lM{)Y0Os&D-PvoU?+>wq9q$QAe&@p;$A z&!5ezOuyx>)$W0X+hv0$5m$&_9_XPA!KYJ>n3Jc{R;Hd!Pd47Y{HQl~v)UcXvVMI1 zHkgn7xbaKv^5A*rbQRsAJj}-jCrrI9E`9y|@^<%<D(95Uj|#to3cYPSp4v@2^<?pN z9?wXxbb)h-*v}E`s9X1TZ~dj*R`-nElWrm=*Kg6jqakx>uiNo%PxmMKG@-KiC-tV= zByY)0?T^g5M?0NMqHig;ofXqpb%R_>Wk>AvDX*RwS_ha;ABd*5nZ2FjG8>HP?Lsmg z28<UFOsZHi=!1>D`kI12^WaPs2G^P6wM2_pp}o+)%7Gn<HlTlQL2C9V6&ouXu!E9) z2R2O#3>F;+>U&0(ORAMF=9JIBi!J~S;%AJ!htp(G@J7l*T!aI!N(cxJL>pBZw~^nm zk*8|m3u)milpL)Mp;V@^w+yKt0<&c*Twctxp{ft5F2<wf{g%&41sKj6Ed*DL3Ri4L zoark&yy+aJFURqt;Ek8RZ|O1qT6SHfL7ebdL~j-p83l+_L2`yFXM=c#Mo>?wV(4Ob z>0<7?Ps=OVf3p6stmFB3Kw^~7@qb@+jp?t6;J%KH(;8>uhp*}*A?HeaLROtgw{&Ji zL$RdeuI9weSI6P!#v#etAz0Z{O74iG(~nm+K_w8fa12R%qr?yWG|9Y~W8YsuF9FdU zexEnnl3VdT^?Z*%@*DR<gV!@SS~gxAhK=mqW-TQB{9bcEZ%mD=K=vQ`I0S8L6Z0}V zxUFad^`Bo)PAr=R5f>|d>rZ>z9c+MCWjxxSZfvZuIKNCn?37mgC@V!wy<qQe?|J2E z>LPO#y39TjQ17cacz#R>58&|f_BRy@x6XMPtpibTYpPA7`Ev2NFq@g*Fgz<kv+pMo z(_%*sYZak3qjzFxzsQI)dv5_<y!pDhS5K_7N}mZPkH9;FGuw8-`CVT5o<!Zx^R*-n zPokaMN_*|nM&u><VxEmB^oI+4S7Bvl2}jZQhGFx0PFb{%EZQG#GOAWq`ZL-6eyex- z$SLfro)U{_T~^~Is~5~){RAK0zYq-_q*i29P@mbTE*(@?H;n6fHZ+|&R&H3F)z{mC zpQBWeWaqMl6Vx1}3O$nkAaE78rTQwNxG!7j{9B>!;)U;VQ%H0z$rUyeg2$P)t?)!s z0zZe+roGB(L^qT@wS`au=2zT^xyfgU_$Dt}ZW;3%L^!s$!%HmRE6ys+y7%IfJ6~re zf?cVft~Ohoxk1_@>aE1_4(zRz!^_49!)HvNNDj??RQIEQf|TytDVSet4j3q~_;}4@ zA7N2fRAt|FGZo>8pG4P~GiUc{Oz33c-nX8fK)tGO&|*-o6j9HQWBsq)Yox7vR>1~v zVw$*b6?R+=0s`l1-#?&k{fT}MyljIh<hdLZ3gV}`GG{%M?Cp4<hCy#RsnKD*0t)=Q zuD)a)6GN>L5r|Mfiv-8?3gqWP%pp#mfb0l)IOBa$vB6#gNeU{+ehMIlJT<T&48^<> zkTG%?sr$0lpGYFk-s~&vph(fYgTUM(4mY8u5Xq;xJ!INjI|6(8bI9XvW9a^2uT?7a zC)3CK^RYS?zhbOz7)Y7^Juufqc}t@i7-sJILQnNbORiSK&;WxU%Y3ZuEaj=!1ce{I z!D4RF;kjhLfi}*Nf$pi-$LyUHr>&F{Vn1nlYhr;UuiZ=_#i&q&Kc6;(8tRg!9Ni~@ zSVBxS*8_nLF9q`NF~(ef(sVFM^#_B)>mUlKSEDN)U*`Jt5gscH-WSGH4&vve1K!~T zkhSaaq$z`X4`<r;DyuCo^-yjkfd*+EO}+TP(h9^^@S>W(aLw;U96^2x$`_qp`w?7_ zMG&e4xa3I%H^5>}7wQ!VX1f1|etyuQWW4d#2gms7s5j$n)a*{((YXE^1?&;Kp7b5s z1}L0pG+WC6Ef~Bnwpgz3r<GPv+hJV8_GtpxR6z`W6S>%=qG@GR8{OxYsg-%fa43QU zpI~K%9fQLUD&EAw?UJ=0g#BKX=r3+US&b?1dqvdv?9v!56pU8tQ$1AHVKEDHziP2a zVT<|)M%szH(-mEbdb}F(>)B~NFY0W|bRY1&Hbr)p$ztXwszDor!7()6F=MnVsQ7Wy zJbVYRd%KOQ)MT!OBm0N6M(x<G@f;wmnepek01t)4BH3l_I&wKuZ{N$sxcdxRFLfXS z2-y7&%403#CF;?^qPA7_Cm8T8zR$Ci-~LIfH-}y6yyEkzqBwgE_9`g^0%Th7dfJbN zPLi5+LEtpdC_F(N3fkJz4$3YpxBEA+gP{6}-53amwNw#}svInjhltHyz@piRT)%er zAS|YI!8zgI;HAEYo|!({GhA%wh=jI0aiTPnd$=hr>u&tuJN>=Ksd@hsN_K(W)F|+7 zCPeI(^qL7+xv^^LLP{~nWx%B|wZN%YY>ij<dB$dO_`DQ33Cc{2J$V;Civ+k#!g<#i zA{q<z`Rgbc!>=ZnIA83iMK>&xq9-cG#eL!rbeKGu%x-g;Yb(LmK6=F5X#=Zx&Wb+b zmLrG<Cw$PvHa9kNL#swf4etuzC(i}WlAOM}Fa*;ajyM!&?l8G|^30IuA3CyIrPS12 zb~`hRPUM8?a?e|CNA=YfL6=f~zZT{0cX;-S3SAD8VlB+CLdCzSx!iq9uI{|U|EkSM zaEQ1Js33bcqVC^dJ2Ve>A_EI_mywyqnP)lKefUAFd?5kpuF32?&sYX3(qR@N=hA+c zD^kEcfL$1H!4eDKHz!HD0pn%0>IK*N2o+oE)1SRU)`hrsWfMw2APzGizFu0V8ymx% zmui6KGD7*)%7>HA0}O$OjQ2B1GO!CT3M~-FW<(Hs>beUL*P*~vPq6=JqL*<74&Lu$ z2uG))9OtXEnzGg04h8>7S-R;ikfAjoVlXZzapK%=A876Lm6^LW1**CwsFc#R@qweA z9~1LbI%D^)gd3e<tZr%}RX&L>Xnq5fKQ}}EHT9LBA2bD!(bI@2=4x>&^*v;VgL5p( z=$O2_kBMeIYGVhsR6>=nQFCp6^`ctnk*K)?Lcm!8;R1mImV-h%=}6;dITFq<Q6anD zTXO(<jRMX+?*bJh8KYdx<2u#i`pddMU0t!vK7VSE>i}fd?P7-xx|)LyXP0#u?PK~+ zgM<k_i%IhKq`@HrP!i%i-l-X};Rifo8XNRPMjOXAHH$4Qo^D0WOpwy|gXVQ6S+TIB zv-u(<$Yund`mw5?mr^$DJK2K}<*Nph>o>9Mv+bH%sT}I2Z4$Dply{*)nJ%-LvaJ*j z<L#@L7FVqj7RBBUqy)S+Gn-O2Y&%)|)U~q-RSkPtX#%d?5krQ>8*>{D^3;)fy~`(y zBU~^vGb!w#gO|@4sECxZ7b+Eeca$9-{%QQ)f(I|HVJVF6H+!Yx&mbXy|5l_q6x#Ur z!cv(3G+Ka$q;R<J>`}CxRow4tQW@<6C0EP))99TNJu}c!cX!eo-3pc~J_u6jy#T3H z%epI1qzeEQpY7${9w5%+06xrzh=M0C(YgQOy16Xo>(ReZWb-NfJpzmGc$$EMRkFaZ z@xMm2V)1PR4Dpx1rP0rzRpQT}Pnx5L9l1MhFS;+!*;RJ+DxRO9pJ%>nd<>u0l$1KT zK;OFG0@MocAT_b?AoHAkhUK4cA+IMF{~n?Bu9zQzTJJ-!Qt?BO!RP~^*W}a8M4A9l z%Gq8HK<ayd^!I<Wj0RM^`?u@F#|hO}|4!ZRH{deu+#BWLw*cM3H{f;s4q3)d=MP8~ zz<5l5Wd9qDXux>eTNgHAEosxafwRA{1r8<%vDn55u}ppv`u~5pPX9ySge_nS&?nE) zXZ<hM8ijz*KZ&A|1Q5lABSXfnG#c&Xu;*nWv!LNBS;}^Atmh#NR~oHK<kNX<LzhCf zb<OtP!QRP(#o}Sz3lg6O--FTH&pFhOP~H!0#{{q`^ky@0qm}ZRa9Y99*<s<l>pN@t zYUz8NBB+E!Pl6jCWe_f}C%!5VckHQXt~`Z092JUMM6UdjYFzA2YW7kQUB$JPd0H|V zydwM_rh!}RH6q_?xfk55Oy|e9XDiIQFT?y+SM#<$a>qbd5iQ##M6GKjE9+FBH0w;1 zgk5TJzD8Za($G>Zak^&`JpnEJvEK>LxiSj~`)-W6bMD0l4);Z_JUQ_<<-Xf9k@z|} zznw13%+xv1Qk>Uy*Fshg8Jx|&WL3a-Lil{K3KDjfI5|s0k^Xx6Qp}|3NyFp+N<j95 zyqZ9_eXtYSiaE$C%?uWc@rnEgALdQ*x+$iPTZ&Qv<_29zmBWG5QO@V}vAT23CZAS; z{8zXj@Y@{_dq+CRr;t=8BC=6-w&Ip9?B)sTQyXVc^e$a|;>QHZmzp!Zs;~pX?iNvB z{X+B&HOderPj9CMTsVGRHa#8qQa@k3PmwR?3DqNnHDP-vL-x1SC&S>GK4MN^LDm>S zy-a)@5GXmt7vBocc=>2<`a^Eaf_|DwcZ)nWzivJfq)lV+x#e_@<n`2MA9b)cUDo^v zt9?fM)#(KXGR-%#6u0+^ru<cIyZ+oSul6X~kV^lUfp#mikM78;ck2;vnqu-(HkU@S zF5DdoF^!|}@P5Fa^=-bxy4%xivU@Wqd!4HshzU4OPb?)47%XU~WM6>rVr5C{dOy!; z%|UXfaIe|Hxii7VVv7MJUa-){@Dtr5iV3wZ+i^)*+p{Cr{kX@6MN3=+Ck=`)p@aXM zy~ahqX1~c<P($cS{B=yKS<L8KT60u*rWrhZJ6yXhzXA4{JTAfQs_PyP&m4Kc7)XAe z$(wNCg;jdMv2S2BWxBP_7S#Y*+xLz;L+lw<TYEaCw2yqUMAL9kWxL>LvQSO3=0F3I z9U#drb^26fixecslSza|#5sBcmu8FYPff)Vjqjue-|CTwbT}<qoHb^6OeK<dGOJzY zccgChrgL~65*^^FeetLS*W9Aj{3jDQ;tHYJiuE+*M+x)87#KT8p-MJ)XxbBS<B!~_ zo%v4sH2VYxK`HQzzXk2sDD!uRf5dtwo!EPa92um$`uTzPXpY548o(&Swz~~rfhFC8 zB4jb11e3VhJNRx!Z-}Oph;MnS?=r!8V&C4}@4}G3x&IijW>6k*vC7&7X4ClDBagA^ zHrrx@=6#_xbv}*QarLr!J2oPc+o|c7nAt!gm4dE{v%<2a<>3}E|M*r6smv~8L??C1 z6)Q?zO-k5DHnH`qQ_RbJyRC2ZHB{32p>V<i)iTCmyZ^C1Y-v$R_Y11v^kd;v@VC|O zo|72J$^A$()Fu1<Wl>o%x${+yp+#2&y1E)H(XzJ3cO;Kr6QGek_1@pHVp+d0Bgf2S zqv~#UMKpu)IQx~Kbs6}agdP;O1vxO#(a1kL(X(#$+!kDNZ0d!#U%LC{=Q_SRSgM-^ z-<=C3w-b2w0P0P$WlVfLj(Q0Oe9kXx-0FF_yN8k_A&`9|AlOh$U$zUPvBUpB!9{bw z)SPA@tKmf<hv-I^DB38w76%{3jkQu{I0xzqj>G)&YBv*O7Vw)Mc}wJbcqEg;Y_+zr zG&D!ajwP)U;Oqgl<P`6X8uN3qVwVY(t6$%JA1fNZTk>vO9`9fD)s@dWuiWpkWS}%- zR;n1sL|wi#G55-J7^g#546@>^zGg%@uNX4xv&b$jt6GZvDsSeTkwQF-%wxHTH6brc z%<yC8Gwv%Hj+tbYH8QqD7a~^=W3Zid==w4$lbfEtEJJ&<=Mm}kb*U{squLTquItM- z*z0vb68SG`kg0jx=8R?5<0cnVYn=6EnFZF+5=CV5K#!+9DMCJes7#ga{eyGwS=&3K zKqe1v_Vqm*GJ~WH@n$qz#u7gVGVwwUWY)}1^CEf8H1wEqy)NT^N)HT98mzT-eZXMD zh8<tj!S1fb#9tIVH0sg`Y}55+a=fNsh7mZh=2T;5mdl}V`uM%gi>7~J1`ZE)(o>RU z;OM6xpVebTSVXV`*xVOe;sn)bAz!<A^hx*$<gso`BT~w&Q$~RKc1rAo=6-S7W6)W_ z@+WHjV&PDWr1u88WR0Mg3=vdr5CSh`yJ{4NWeZhu+Agkd5{?~$8^C&?h67dtiNWqV zI(kmRX#so$-XI~Qk9lp~ofcU*7M$KMvo>18q>la~wsWY5wrX13r*CO&AQCTB-&^Ng zT-b|@JE@|IP0@6cr0+FoFT$w0#0F9++(5a`8`Uq{NXm`@iI*aojM<0`I1xER8%6-E zA)X3StgoV$3Wg)Q!2XU3TIQV;u0$Ne^@%z7JT!$iOX_QxjznKYJo&O<R!2g=MG}~y zpCKBWNHS$F7+M3y4;a-zy1|IP{;|X_jOoNMk@*tIfltWsS_xTj93miFMU)0|<P(|- za8w0oLy`X5k^1r9F%>O^qbUv3k-;55vrru@2X$+}EBj;gK^>+BTnr>sn9jpB(Re@t zE0MwLLY^3*s;PElvT!ugc??zIcnXAXd%`K99BBdtZC$~5m4(4f)nO})jSo#F<_ngQ zk+hNF92-csGf@l~MbOX&V#_qT104l*k@nTV0@eWoW4eNka!~UmXOO5x8A=G+i!qK) z#`;^GB7+arl2{6zLycBJfwZk4H_@MmQ-|U!{?8DRhvLsf{>6@sF`RC21i;V|8JrEE zs2UOv$Up0%FTwg(>tE{A1PTE()K(UTyKNxJ#Bvp-3C9O+Ll+|A-~q{@A_i#?75d*u zMJu6s3L*?78UvRB_#=b;1w%&W{}_>{sE`f-e$1g*|7}ZsB(3l`eBh;Iq@XvCzuO06 z{nsOj;=khqPbVV<VBZF|14L$s#0Ty}@B2&SYARv~mOs+y{zGTc8d#o!C<BT6f3_Te z5kRm1zlaRO4J4xfu!Dy)guST)xQeEzkO&-)A=<xau#Eht^)G$@^4kB>_B@<86rTwO zU`tyuq`zFp>}z;0t)jwT1N?)O$+ABd%WxUFkjNG6Z^wgjFCqUQBO*}z1@P>2u{si> zRvfHBj9nv%UN*8pB0t?f?cjj+mM>8IrV_m@WTN_^oq1FMk^k9|zlQh0{gDRaA38Ig z^~a_IL~fvT1q1sA7WBHDK=p#$yTDMI6w)MWw-qliJGA{{##VB!l#JUdUIPAP)^&!| z-Eli&F@DNsWW~E*crll~?rzo94tKV+A#~l+ZMFRemQ5FlRpcfc&g`Uy5i5vLD{ydN z>di#%1Fw2#supxRi1D?y#2xU;W0bxYRDukvqNWwT%!&$Nohy=bF+6|0jHSY~qT)x3 zs!m!5H=dP|Gr1Be@&4?0d~=i%s{Ye3^&p#&9IvaqTG)FVfuq>2==H{TPRl4?UU8aN z2G-j{IH~_PM#$pK7g#W6_~BE3-5pk)r}x-(kQApQp<5*&HMx>dG#*JEbDnWk)!8BV zCpcEs`>>u$`WsK;3;MUjIJMEw&_g=SYcMWzjN0E_>ayBaTupCYXwBc3GZ1(&Pklt* zLoR;$>|avzJrHaFjwVdvh-i1Oz2_UbEA2k09(_Fl;mhL!egg#YZcXP0jgHsvX1aI; zUVigGiKN8rorLX}Kb5<!@N%meKlj8QS4Yp_U&7vdfzXHJ41pfM%i8gpk4N;QGA`Kr z#;HLj`-F89o1N0#xciCIQoT&Uf?N?7-;=5+0|LG4{_^@<Y!BUg@*<GE&BCIkvRO~Y zTycs6bZu)hsv;+u_#%t#nHgBxmJ?Vpx+6D++g=lKmw+G*ISXvO?1sL}g%=`VhxOA9 z6>FT4b{U3fTa52j!lOUPXF0LXx^SLh%^OO0*KMtvn~(27ReqN$L1DD&M{eB1`B`rZ zC6ED+?2;=?N<oXL1Fx#=xFk}ATcHKv_L<jI1^AcG6etk~Fb&AsKd}rz<+I==>)|xA zhkpJhRxt~!iq#osWeyn@VhVPK&6$tTx?crE_L<9|T~UL@<xR+sTr<VKDsDX*>}*M> zFGLj<Wsw%<q16_ST9@hMHqo*h)34kPjQ!?jx*nWh!7#s$eUPD8RQLwP0HF<=hv@8H z_5KXvVY&T9VkZ+NI9y{!o4q!6H7T=aNIMNgkQElsp^wmP5>yk5l9hRr9XK2{ZX1nF z+OXyK(YOXaujQ)I`#F+~*~H(r-&9qC27OZ4CGd_=ul3J0a&O4B;9yPiWLhfCHAjxi zOgpL=gMc<uBEhh&5wMA*8>HdtF5Vrm#u0+BY+d9T5D-kf*vlE<4a02sQHYE#zs?<! zkq;Nho;*0O%*ZIua43_*KQA}AueFWv)p6#Wps*U~({3;}Qs3_nvKZ{TK05E|Ctq+C zJ<ckW^fF7vU?MJeHELyCGn!YL>L!VW-5RO{TQMsPtFT*Phj3}_8o7C8p-S)dVNv$? zs#j<jhT9MV?FV4lx&lNAUCvQ<2Z*Af4uLV8oGhK2AsHjLF;o`5^xj_0&!e^L<l?oC z8Wkm!TA~(>_A3~;-g&_Kv|i1)mWbtKLmD$t>-*$<iLEa{9y5SE!_y?;T<VzFl~Ibw zkj$zi&;yJbrPQ=X+euC!Oc0TbjH#BXSmbMFPrHJyP=odjUY-v2L~Bm*m&HP~KAp@W z@##rUGktBO!vHG<z!Ao*?d7QB$rFLfVi8Gm$VAs)D0}i!4qRNG4EIs4IjLM0=hM0j zG7FI(T5CJA(u;#j5DkQFJ#bQ_4hJzQ1P9cqGz=oG@rRCpGOeHI=;FcE@tN)D;tbA? z(4ZQ9EkL}f7qGc#kbt8Ja8RdIz6NgLBNz{4i5~&cY<Dm$LN+;8rAC~Wobd8=WL)ee z!KK~>FEv(%q{k(V*vZAWnqg|e!o@?D-5Gr?jR|1W6ig8<?H}|5IA;3_2hgvCH$_=0 z*;~T&mM#rT_Ia!ozh%vvOV*TQPAwEm$k3*sxzCfxs+)R-_gquF3P*(6om8i=rddXy zbs__Mpc8r%5q9pD)WJ-U=!FIzxdVL^BApsG&4&aas*?QEL%~ca0zoQx<al<+6)Xd0 z;5P#sM0ESnVdb4|kXbaXJrhFg_`ipZ$O9!x(^*)^6k-T!s9Ahq1PKdfL-@K)RCcik zh5&8#Dj6W+Xb}meCRk2S2lSKOMV*>w-Vq5jQr{G4zs?l`(pI>o=VQ7FdWh~ww@Nh? zABbE9YO($fKoAHZ$a7sLXGIhf#F>g|JWT|P#7Y$$6G9c#scUQE6$MrdCGgQUkOfl( zA}Bw#l!epCGd6?Gt57dH*oF<K&qyE-WKyV*#x66Y1Z8{a_h#O5P`8CYg&D$G3`rQ_ zvu+Yk9)n?@b=>4a<5ileg`l7n{joTIo(!yl4%>v63_KIU4mAxHX%p|I2lPP+K07Nx z&(cy7nGZJ)_;DURs);ulSdpA%SyTaHJ3+~x&ZTr{I1_7Ja8eFiP_<5BLI@j4hnYkc z%p^}Ojagwx0m2et9OyK1NRwdPpE3#C`U@rKwWR96|40{jjx3%HMV)a$bP?<_E=tfc z8VoBBO3(rs)7pqUxLy)fc&ooq;4uMG9TKL4G!b+ji)KYm4$uU2K^4I^|I-D{N<B@~ zk^3W;AuLOaWZW1W*MjQ`4~C%JTzw8jcg#Coa^hcA`vVNK0>ocR02BsTMtwpA8_9$@ z2%t9*0TuZ26BVc|oJpQZDcm2+<iY)I8D=2AWDpCD{{Pr9Pc@bK*PsG~rt3IRMP!I3 z!npr&BDTd}fJvDA0qoy+{SS8mn1w!+`InOdgu*w<#ajA^eO5#h!N2`QcN~aU38wbu zXz@Py8?!Qm=YNorDn6CCr6ejJ?*2dfiikP@_2U&P&@yTaO91!<(xycKL*1V$L9_#) zoc?zx%Ps#;cG>nkRwAMkv7JN`!N&S0XUPm>Ci>e^82-!LPFCXmG#ON&B>(Dbz)yu} zk`pM40K1C++l^3IpBz9O%FI}=456S(JMX9fA)5A65>*J>Uw|6-|I#iMIEyk&$pINb z%>M1db)ha&q@{muIknn^)H|g=&xDv<W=2%qF&XUie=~J2VX>el#CR{72zL4(K>zm} zwirk&w3ZxzNW;G(lD<$3_Yb~?b*X<W0D%i>!YC#H(i(Q+Twj}de1Ur_(T<hH#tm=r z(kN;Me_Qr%YxwG;FLUHrx_{R0ofqS^F66Y*&<m^FMpKT&McTQnqQ!}zIQ$iUG5bEx z<v>O)InpG|Kw|Trl_QRJ>w&EahZ`3}%xa$L|FwvJ`Hi%$1ol8YTais!)ugNHw0(VH zXBoYaCE*J+aoXm&U=pJ&vYNmkjy26aAl@CKlb7IfxjZo{yB1g2d^}>*SpO+ZVmB4< zY{5tD7fjGil*=lzL_(QKd$>gbd*yWu^Ww6l#s9qQvcK?>G@pep1=#E`JDT&nIo<(V z)P2Ojw*_N8Z6l>L?LeyJK=y1N@@8kgSpY5{A7l!wwH386*p#X~(Ia8d5ka@t>y*FA zF|W5gwJvd4J!^C|lf8Dy=aE##BktC!q2K_r7`bWmqvx-foa{4HB`37B<hN@q4@AoA zT%6e<I=Hml_Pkz8U60weBLQA+3^nI-pR^LQ7$(PBz7pLsvaVs%ZHhsJzE3Ksyy9&I z6zP0S`TXR;crFDPKBmhQVcz5tSlPtOi<t%`a}e58VQs=^tgCtR;9(t^RA`Qu{N_?* zrOob@$VG#BU7P;L!@f~X@X;&K&;;Bcn;!!IaW@!m_}&aF3J}nQ6VQKqcp&rN`3#@R zxFd<5?-iGR3W*h{j=|~K?>U%>AYkd-y?BZEPbXhfF@7}^Eoe!(zUgj!`sQYAq?j(J zx<W?rLWVh>6Pb3H0ZwHGtaCNIy`5d{DP9;Q{|vn`?y+rB@4}FMxzXor+p1wWbn6i6 zdN^bFd?pmoyEna~<cg%YGiT0SxL!7H*7HlBE)}@An0!H;Y2~nr3415a8FIT*6Ck)6 za&sH-ZG3cq+V#rP@U~mWl{-CY5FiUF-Lqf?-ss@OemrbY%UU-hU3Ktdg3v-T*wjUd z@pO`Ldtl6(B5lewozoRqt&vYM#~>ptd|?o9Bk-IvPkq<ovASB?@T_|Eh*T_<>P%m> zIvS9g&+sGILG94x!HyNLylS7hx;t>|dGkh#5pSA!e_611RIcelb%g2aHE}gxTOT5< z)b;An>uD{!vIw#xgnz)k1KlEq_+-cXNw{ym2m=a<<^;jd^y0$W>6iYEz?a}Ljm3<P zVjsipn~dX2Q__2T)-cVY<3dPLT&dN{n>%;S$TXqMrGz54M#}k^Ij3y#7=d5bjgmlj ztm%NCIOdIi-u%1I2ici4p}faseX0rM={Km2<r=rx4#<tOy(D}CASvF=ro4r0xyO>V zcAK(0FeVp<Dha*B<)M`WxyPrH*ENlfSI^Q{Bxn~aR^^?$)YtU9=8g5GZvC9}oV<sZ zr}KLk4vM9p6u0T=)o?$3?o$#cFmBu_gp|KQD5+tI(SZ2ood%{qt&C0WXgnTH{*F}% zD|vcto@vbNP}@`GR%a?db7w*xRZ$_gxXcdgE1eQ~lloXzayyuO?SWlm_yC%Cj^F9y zNpE_Qp}-M)=c;lrYnUqHxOhl>rDPHLJ+>FuTk-HZGh@E|89%8?`!Kn1W72+=#<SYo z87n?#%ZvYTu<bUm@~Nw{wch-KxOe4yosMgM=#C~YKq39A=lgB#gu@cV7?AJ@wG_K+ zj6;D!tJSm$EGWazS!u$_WgcPSRx8t64%P~WmGx5qRIp}Gem5y-g*H=<e)y^ojPOwK zshUG`Ka^6<6?;j}e>F-`K4m}^C9bsGdp>U3>#8t8yPQ;aqKxL1*8F!QqU)~l$JWfz zLLv3S!H1=}BRlfu5nqVFRe`a48p<4j#p5C0Qz+sL(Hggj;ZfJvyM~r8fvoG6>pV2x zZ}A!9U(#yw7eb-ZLax2l9G>)2CG$PViv8xra&~&eX~`*kei7sDtyr<7@wOX9X~}x6 zU^RR0oj&hLDtfK^^qxsWIyC}%q|-Y}tf!`lydNWmoaiHP=nFlrEg*zE>i4;mB4+!! zhw2|$AQxoQ6tcfQQbjXPO?fhZC+(;Vrmso-)D}`D-|}D46Tg#pxZSDez!;0A3p>ER z$t<!Z|9L(mfEt*igAWJ6i8R^FFxpV5Dfc=QRoCDy5OFw);=A09r@YE0^)>{1tItRv zHx+Lxi$um3<Rbktu%f*W&M)4CUW&=Ea(|7#r9&Kd4Y!z+STp3-l-w7UcBIHlz(79m zd^E+2tkG7*Q$QA^IlMCz#d?=($=cvjet4;4a=Q!e<zS!Ov0DX;*4RTVzyM<<QMj4u zZsBr>ie4x8s0j})joQx~lZ(PJCz&FhLei0X@>$5zCmiUQm`vVW7)O~oUa+22L;>BS zzeBevO{PI3FdvS@%_r+ieSt~Y-}Pg*FvWYbzz|_NJH<<ZE`TlE6lnIlmL}P00@v9= z>?EUNwjA$iC;8EB%>e4*vMbLzpXKXAwUhVpC4}p!E9i6+V%$c&!^o>ZIaH~gVxuCv z^V}eDz}+!H3SPM>shRHAsAOj~?!{m_=~0-ZA4ImgMO)h4n;Sy;zjtL{FP_(?VreVc ztNfed<Z)kj?G$O{q?^4u+gy#)5mQ1eqtRT>g9fHzZ|^r}zDElc-W0yA01t;l_zzc} z*NL-uk-z%Zkc^HJpO8EsZ5$NFkZYpoO6iVof7_-R`6>=$bQ9#v1%Fd~xhVU}f}@~t zy0Jd0P<C6%3aB4_mI)3c-(tIPWOwbv1^EqQ%`K7-{F5#7Glos~W;kvh@#@GgX~MSC z5l6tVMS<6k4{ww_kX&2*XnWVa^zLQ*RDC}4&evc3?)3^crew`Hlh^jH?b^l3V#78I z-Z%Q$cy>5=K7`;md1)m#la1ff3~LU&$-9QLQ22;4o`#ulQln<JhN4?fEc$-#!jx@o z!x~HJ<X2%1uEsFvnT0@kW!QzUlg>qswdo_JiX|l#ysFF8Y8xC87x`&<yY*l%BZl0O z5)PUzAAQ*B2@6T%KnvV{UUfmkiBWrQkViaQgby!-movO<*rSkn@`hJ*Q<(}a4>y+k zpR~W8h+a!YwNmib&0lN_+X`Ihxv>O=bo3zc`7<0aaHWxCbzP)>Q#yNkc$9vF+0mOn zY0?1aMpR_AYul3Y@?NJ4$|<s*=IHm33sR$y;zAv-Fq3x9p~D>rrrsBwjWD0F`N{#w zm&S#Fh(87T9g-i3@<L15f|g>pwfl&9SOoXNFmq12!$0Tz$LxsHj{T*hEmyY*SL)I6 zndX!jELKsA<hP>J=?CudoON?&{Edie;z4;Dlhk47mxt~4uEL<JdN&DrljjXJZV$Oa zcf84@>E9say2JgHQL`P<ViKgkpX*D%g4Rr^-wP`46+gEm<;<3n^-Jsjc42J~7ZBgz z%uW>(bQx_Mz-rK;x%Osoo`+W>az?i<F&<iXs?YjzW^61%aj$`dVyp$%mGRTG8Pvut z<6woRRbx`m#27U?J?a`2c02WqOK#iJHL0?bi6U$l>?}D;n5Gd_04|&@rK*?g80P}$ zq{QVw+3mSfjUS%<t_o(3VWeTk`=>S6u)=e=k3~iznM%wTD~Gj~K*sy@gI8_K=aMmu zg-F1w{z8FLg33~qezA(zY)GZey1myIYZQ7!i)03?JySXXxVEeO5fr(pT?5JIf%P`X zaOZ7A8;qCkO9XUz#Bal0y#o+n(fJFRUs@e4+3#9WL+M+*wF+c)$5c1?-S}*orV#={ zk2k~{JZH>~oc0M+&^&QM1p!GMO^p6L%?H6b2krc5YhtJl3Hy$z>o*zx#-i}9d6P<r zptJ<)Kbz=+sq)5=LaM?K@8;H92_V!3WwxNPg3s1cYix!b=~wSQhZzN16<8s}ACmy( zsASiTW*8$CXyL!FnNd*ic&L;_s|jzLSkr=7WIbswp6BT74yHn`b5=8vnfjv9!v5CV zy6J@J3mMP^f5Mo<o3&haWS=dp!H1~C9n7uxB~tjdYTy?lWuBo+NS-n%8Ek;uEr_48 zCQpo~P>4-!y&B9{F01^@_Gjj`ZB=QVtxaIF44fEYb!7fAsslxZzj1JHPnOG!gXNFA z+HuQ`WGNrEdo_&<@*Wt%TCpA&vvH(w_bifBg!dSJSbc^!>KrJY!^*&gw+eJHIsI4P zW3TR>PJ1x|ZDb55(VOeaQD7@wh2ND3a?PBuQAV|4*BVZoT#iS+69&Xe6L3sj2L?rn z6JK4Z`5<;Kkp_19?|8FF@TO<9_2rmb#NZCYk+GcWJi%aKWH)Mdl^_vDh|FGr)s*Ll zWS5!**IX0GCGI1!Ku3IkKOd-ksJB$K4xB$xx-#(*n@Le9rkJ~6MEqpWZ`tN<AIadJ zB#kISHM3>GJf7`iqa(qj%Hfgq3?kO_jicbxMv?O2vbN>IfA6Iyw|xjsC_DokBYm^Y zpQ$>EPmB5f14TO*k}H~DoMc@?Nh$rTh&+s7_|Aj3n$a@@A^}#*w<V=0T=%SCL7EGx z#+D1%HdTuKibvHCy;60q;rrY&7q<WvSD}zVBI9#Ni@+ByBj7oFSP3|=6N9ozQa%Qn zsZhBLB%#nqZ}f7c8{q^PIX8sMDR4mn!p+{gB0DA?ga(Y;;!zP|iHIcC@Vy9#loRJk zY~<K(9Fpn!9ZlW|AzSuZe69>xnp-rNsktVMFb;!e!ge937UAL;%@Xf7id&|QeE(hL zlH|izRQ`~dg4aqx={D_htu6yKnT95T<x~6-M16_=eQai2%_rp<edq=|;u{dh^)T|? z&-h@4+Qu&$AduAYY)g>4GQWgH4@emHNX`P18EnK$PGxt5(fLabzxII;0SmE+5S=?A zw_~^l*?^-{*D|HxfWsVcTUCRjQ77Lq1%WRpP?Ju4X4h^z@i^;D90)?<-9e3%(RMkq z{&Gx!ZvtJq(Q)t{SYw8XRR5Y97~)ue2qV-Ni0kP~Dd$%!6}w%?>EpzYMys;Z0I%L_ zS=;x7m_QimsQJNx=qK~6FawZe4Jg#HifxM!25bD8(o&mPSHb0_5&{A~k^02}{r4>+ zo4*mfHMWx%vDd7DNQ#C_{o$+pj-Y=p&_m!jEj4d-4$Jt(uu3i!URnRhJb(E$kV4#3 z?<4+vzQV!Gt?IKUfDNZYNL=}PBrr&R(czjdt5@KUjFigEd$R_|#d^?2-teGudmm0F z*sYcm=NHxnj-xvjyk2Hn4gs4&VeMuDm&a6hWnf5|XI^B;@sdx`1_qK>6&j9|uVOSD zWLjh694E%$N7c5+-)tU$#%9a>l4zSf;24iYb_7$L0?Nk$+{tt~4hkz|V>!^DlCfz+ zK+*f}nCsCHRETI!jm0Uo2LmiMt;S2C;1-^KJSL*Y3vYa{!J`^iCOO2A5raGM5CtHp zq_SgFAp-c4ddj%Xk1qhpWlmCSVuH-O1=GV;g`pfTw*{xDIQ~(7PL);Ge-vdeoU-#R z7PVMR%;}CyUzk#7Qzs+$+mdv8tTL2J-e@K@6gWuAcx*{DC0bd3*-w2ctTF`?T9V3B z`&fGPQCn~*YRcmBdjP1trd^5E{UL_mom&@NtASdGvABylXK#-o$X_#W)yjc}EO2dR z%{35OQ|T91+mhKWv`v*d5^uY6RNWF+$*ah6=3VL6iR)?KOtfq%HM1!bpYa?<MAxki z1J3eSi&twGr$V%(=-b;kN5CmNsXUzKut#Dv9mIfE5WBF-+?+Iy6T~sGU+P7HH|In% z<mG9Kc~29?`;hJMN0e?jzo59J&;*pzcs|fuG~tT_V#+V2AC^ZimDy)2n`&JtuJoU{ zwnxGVD41gQdSKbb?3V#$wFx7Vspz}pS;rWL$1sv-SS4IKor;9IB+mFM&b*zC0lT2b zXzyfCD970k1Gc*&gNCC5X?MVH5CoPP{3sp7z8*p86Xg6V_MwZ0H{S_=@t@}t%k;n| zk5xs}qDOkP0^@SF3hf26(~$!9fV4ISZ6O5sX3hGtQ`6I!ebN0A7i-J|h0UHt0}jVc zUDD6GG+gyX2CZtLx|Koff)l4(kpgkMN^u~Nb3#=7z@_54PUQaMh6wFlx`6!Ka9HGA zg?c0vVU)LWA7;ir^%rkwAG3lcn8}jkPB#UOK{AkCC`j<$O9+I4CKj-q;;*7va9XWS z!}*m^6}`bp__)Wd!6476H0L_&-x-A9VY07Txrc-sqlxdG=QbxtD3p}>(YQTYt0Q2M z@>~7Sft{%8pux0!Qb3?X)XcwxxJ0d?9y`u}zOTFG#Xv6QJ5&;aW>M^-vqNsU>>_~5 zEW3<tFdc{xV<gk{W`Ly$>iDBj_z(|5zxOhY3Ok{|?3idOh|(4aMsvtuCDMVQNiEK^ zO7gxi<NolMs1Wi!h@iIiJ>Ef}^)9@4cBWAQ@)>IS1S;k6=qi&Dn?E?>igm<{A(s`k z>G#(F($k6bM8ivQf)>{H>Q5oYER|JUWYB^I7eB~XvhWrPkWZ0BoO7ccB<8emA~aWL z_o3FT=!dk^y$15f<7m9|9g+nm1Lucg7-T>ZiH^zf)^<Qu_BqGN(}25-2j3;5QmCyz zK<kqrlI0?lU)Pebk0!!fs^f!h{l2IyO49kGGO#%PmUyQertmkp25^qb#Pj%$RHHeB z`4q~9#_lR?v8X*8u`R{!Z*m1qb(ahj#cSh`UATVyU{QiBfuZ4f;!33{#rP<e%mI6~ z0WA&pm))2Vc7yRSVaiT!@O(JkHUC;@o1eyeuNeJgy=6hIz`meiJnP8CgQhs|is2y} zeO|Qj;P9ZhQvtjlfCmB*>}n_&r|tp~qUXI2u&5Hf?pSy<C8TvqE-CbA!JzTiR6)wf zALTaj{`V&0tsq6bg}EP5dd3=Wf}b&2J#YUx)b!h-KpqhgYEo4E??X*2e=?@Btdw!3 zQbT*}20tKW<p*Kf>PFkY{buC@hGMMq*C6#em&dKA6tv2p{n~FkJ^b<L!(3UPOhpD_ z)X5TafY6apFL&71bu^ho<@<hi1*jif>dlRdv3F6jgfS`3Pk@FwaI@fg=j!{+Y{UNX zvexr4QA6>*!kc@EzURT8GmXQ`o@2vNb96VxprzH&=i)n;+hi9SWWhv3e{><_(4eZV zivan&SW4XA;=S{%#qqw=s=@y{;e2pnm>8Gfr6FVYa;MReuB-ZmduaP1)B^d-Zj}Z9 z>HnhZoPs+G+HN0CY-?iMwryi#TN68(*tTukwr&4o8z=8~u1=lzyV_N|t9Do4JiT`J zde(17^XkHbBaPCcXOiqwuzM?0=3bNUGm+i*y~%Sc=dj^yXIRm!1B(@>iylP`PcC9Z zvcZ7|_hjoe1?d`WYPipyMeAd6!Z&Ay|MTfwj}Mzwt|h~4;I_l`psG<By(A8@p5TV~ z>pne-ePb#i;W(M~<#T@b2U#O4G_u<RM}NFx*=TNN5h}tfxR3csg|DYOgU|EPin&OQ zamjGc%daJqf5Z;ooMDMYFT%})Mu|`FqWSICR(*8xcUZlkQ>T*hNuMP%aT#^OPTeWZ zF6X3SZ|6Q?zn`$|`Taxtff}Fw#EWuWFE+g<a&^_#_1TlV>;5wkE)8hGki`mRY0LIn zx8}3kq#DJbn7*qh1=-cnxqWWKcjbLUvE<pZ;BVH1y_HS!@bku8y}-nlk71XDd@^}a z*E&C!*V4j1+oMo&@LC^1evvw5wUXgl!f0b)p9Qs88>m0Rq(&?ZKiBFi?^&0pcXwXD zf%4a9cN4&-`$3C-yiZIj3!}v-Os1l*aQ<ifw#`%5OAX(%SY6Neg+A@Q0zY?p1R7yG ze)*Vr%PSG#_$rh;-DTucvWCBFyn=-#XY(6O&4n-WNo=5R#Ij0hZN==pq-aN;ou;L8 zTs%RK5et5Cw`E|>H#@7xbLY3t&Ie$o4su~Ov0GiHTju<u#@EssRtpZQJ_-e{KdQ@+ zi?SG+W+Q4Co}l=5CM#8Dr@3^W?jo2RTCZ&1F5m+O$V1O7`TAng^!4N6=~$TWNER;0 z?!7=eD^#grA>RSWawq!jSZbztXsWO==Jm<pTbNHNooX*is(sIlvZCirza@40oA<lk zZ81^neMKmtpstb+U+3kXEeD=n+F+SD>#I}V<|XxyN;NaO>n8pmJeS4y*O#M=>NdZw zFADrW4<!*L&9w$LqjU?8;yV-ZMGyFci6m9wfD(jJt@^$D6U@ktmVr2sd2)Gx)*W-p zuDjwUkM{><+03RR`RDDWi7!J>mQPH)q2xE0^wszCr9Dv#7FqdsLkgPZ#n9z$hw7=+ z#{u*N{zC32;JJaU-uI^kyl!pGED&H%#Cg0xTnw+^6pv4EDS!WZV)%C3=u4b|CoeNW zy$477c^;|B`z9_lThNdd0mCNJP{MvVrzt$)^JIFl?SpyZ)E<PNvmIS@5rX`60oUf_ zz>JGOXPVU?-!1G){rYp>^aRd#6g4C>vt565+|kNY5Oaf9--WtEL5Fh$HnW}WIW#qO z>BjLrpCxaiel-T8$SYWozPFaqSe`?c;qF**c4sShcLC>F-%uibn}CD5#;=f-0%7G@ z@P+4PQFoF+Zn(AGCJBCR16jf5L-}z|G@p4ysO?&@@cq37m5i<u;Vf!M{ymsi#+B%q z_?WnBX5G1gKqYb2h;JsMP%^SMllTcM+p3!Npdrp@$H7?Qs6N7*nH&pu$K=<L?z!A| zqr|l_f_HAPzvmdP_?+ZK``zG4&`)q`O#jR!dBe216Qx%yNTyfpfO>Hyo^~_v;;Tx4 z+r2kCY=7DwY~+9N5uSjHW;&hr>1o1AtBZ%HJ**}^I!v<U|FVKVU&G(+)7B?=K6XBx zalpO>=Tg}YD9Uf}U0qN1ofz1^8+D!7>2xwK{#PDoDK_qOk+pRda{I8neQp_`c5e2# z25)o-?zbFqR<~^WDD!x)rXH>+JTCt5-t{)OOs0;kSK1cYN1v)5rHX=uGJzoFqIGBD zajts;gQ382rt2GLaFn={c&j(jdS2f<zHs?`e1H9-Q$?us?-HltFZ7oU%Xfr%*NrxR z2VZCUw`kdx2U4@our_XX54K*ZG+0Jr<kj@pm)P^t^jbHM)$%j2RtEp)qNR;kMt}W? zXBYZ+byMR`jV>I1L-zPO&8UiV4L_ewCQ8TsHBSy|LmroJatp!X0crGRlF-3jWBYji zwkrzUgJ)F1U$-Z~yoGmcD?O)ki1J7k`Zg`|BA2Brb5hoZyF;qFf#v!_P}P$1vx{lR zl{}|InZM7PjOS1(#Z1*VuAW=lih7@bIbILv+OFZ)*w;qZK~#?Gdti*ZPB!+RQixD; zfaeTGMv%6dFXJ7*ZQD!kmHe2Ms-?XZbC^~JjONJQFHmHe&-TrmvUsOQLn!OJ$`ja_ zqzt{fZh)%L3;WiC7Cy+<IZsYY0e_j7PB-m9R^j-G)t=6T{lA2G9V`<EGwE%f3i^;S zg;}BTV2z9B#0pUoK}2v1iuqNTepE23>#8)SvtVP3^jArekKTyR-Uk(TnA5qTa<5J{ zIqVV1%vTjBByvKDMh)dI^azGhOGQiL>N9|CVp(t7J6Bxf*?`5j;i$phSL@6p@`!HS z{fCksL4XwPzShywUPjjabV$^|-yQ36`WFiC^PKF%_89}c&BJ>@$}zNkHoDS?DoPIe z-Fo5q=i7c#uzg8zC&dwV4MRTjB~D5k2?Y4;%>A6?E#)=ZQGF*G&+~=_ridi2eTEW= z$O&JIO4MCGD77rsVM*DEnM2~+9tbZwv!u&|%vXWgUFR$Ua@?yTwufHvot@ynISb#x zq2^cnS^P^G2#;=klWuN$-|-LU!{%j`ZKg?(n8YX1<gm#!A&)r!6i2`e%c>lJ08UvG zJy?$d83xZX6y@wlRIwLPdEEBMRQxS{;3?7|3-3PR&QNvr_{|EN9JrUb6&k0!P;tRd z)gG}qkmOfiJ;Otn4DAo62Fdu1Ri~~2Ja#?wIV55sY%bisC(GC(UeuxgchE$79jyES zMG>hB;y?ZDYly$W_7|RY5<&P48Nf}loUCl?FzmGL7aH*d{cUOh4Obd6+4;#tCC+A9 z%25H!{ZOj?A&z0-Mag~9mznz@@im4aFZmJfwR|``vgi-uJz3E?SlA?*Nu5O{&Z)$P z{#hy0l7`J)b+KstVk{Tdz3`3!O_M`oqK8n81$8d_Wrb)*N;NWofMk!w6_uPNi$^A& z6a%n=^e{@HNI{Hjmg{J_Itc5X>MUIYfL0W0@){*pB)&)bN|gV4qg!5~UZz_i_681u zUT5TT6$-T12eQ^!z~0(pN<UNZ*o+9?Jue<Zv#os05RO1F**y{=fr3OsCTx66)deWt zh(0Qifux0ZBVMa8dL|WGcJ8PGsV=nYg@(LJL@d{6m)s|v^b}bOTs)#uT~cv(0>$ER zL@-@*)No;rAXHh9buc+Dlww%y@cfbU_lgNnf2@at-{riBP{ZqsA#HJ7H#c6APNY@o z>(Yx5R&)*Qro=cDB?Y@hRzYAOoDG9fW8Gf-_3{SW8%*5l1zy%X{_F;KI%xlD9<9xk zapaC{=yh?8<GN?rona5f#FHWW8C1nmGo~7K!KA|p1Jix}%Lk&nSH;1!!5`5>tAJ`X zweJVw1s6o>hmTol0mGNFT1R=atdtvZkYAyQC1oIaE}6DT75wJdYDoU@UR_ksL_L-6 z=fw_+4-DZ3-B3#;GB0FFVPIM~oU)i^4w<+7P#YKRRY%+Il}svB{htd!4rwXBW@Rze zdod=i(oB;o!r5>rfiyx9YYVINNi)?Typq8KwD#*Z?yg8TZ=uP3R(Tq+=KTnu=1HU~ zk_Wi1Nuu5RWn7W_Ay1!?4bU2-vfe8bxeA9xYIDGZe3&CGE#{1DNMFE*7>O8imE#nW z+_%me#HRz<0|rqr!*%iycS>ST&O&{ZrM~=EpSq^N=HzZHkZa?D=-myhZ}l&=oAWNj zEZAy2?_@(-M^Cyo5ISCW9=Cnaa%hTmCcuL@28ooI_4`ew(tj|0zmN#O%#DDw)j}+X zX`#F@Ux67AJ$=H!-qcEu7wQFjQ}}}=_T(94vDiH#O(zI*(S-$%j8hNwcD07YOVW|r zQfuM-uYtSrn2l}T+YsYyD0g&a{fXi?<hPjXZU#r99#B)#lh?CXftg7z^euEplCc^& z<KVwhx-q~|BV>Rp*pk@H#@X+4|3wD+)-t-Lp_nkXV9X^Ltkv2>2yA$C5TuLq*8amJ zkn0n5BEyql+;C0TLE7NW@oTo1r_O!@W~CHJPbpSKwW_k50w5@~pCT$B2pLSNTG27W zA0RuGVXQ*^5T27LpI!)MA;%;%-LYV8rgTLEO@(CijKSDvC&-o|*R-5&QZEb&kmeVr zE2qS&+z*4TrEZfYm}Jby=h8=zRSFm)9W3JAZF4HN|1~nu;+#fOid#)D``ci3RMQYT z|ITf#2=^E90?q__1bLHSdB6=92Vo~;(aM#pe@HcI++TcxeTmh^e$wzilB;q{tS>_` zHo1t)G2xvv$kUY*5CPVuSWX<9j;X_~V(%R-aS*wh<tkKlv8(KWwh`i2SHg0{Wq_{& zw~A2?nTtpad>u45sTQN24&YRZZ4AbJyatj*q$6xzT|_P}My+DwR(@+M6Hqvvb7C1W z?~?Rm0T%b7f>g*=Yv7yL4dyR;D<q+(%zmqXwA5%_-ax^!iKHQlCd5`#Fz>SH%u^CD z$}HNjmV3v+ET$$gG~^vi9yT8$ea{~N!bgoG@jZ6590E@YXeXJmmZ*W?*c=o2t?A<u zv>dL?OH8y1YENmGTwysS?2)6P5P-v0+uyppSyC@Rkf&}hfdgbU73Yr|PTS>UO;DxW z{sBBWYW(Yal<yEUHBP>$x2rxw$f9mf4uZ5VwCV85e*t~Rd(;rL%fhex#3DqU7BdVN zDqi^p@oNOPAS;@@PqNJ+k{Plt_;VegfRLuso1$N1q%MEy>_~5NKGr7@Lgw5yIo7yB z?1p9>H5*z6xN>8U!BcCCb73idgk5{+-K@=*DBBZb*czhM-5e!{mlSUkspehNT4sI~ z;1EZy@86SG7PnD=Me1`jq96*6x73H9M~5>Izu?TnApnK##Vw$fB84~L3EK<<CP~J% zQ4q+UH8{t*i%<+ezF#ITiNJF|?7$yT<2;EV#67lbJZAV)oX7CW$Yzi4<9;Wg*q*Ef zrQM`f$%?RQu1wAJ6cChY4+1IfZ@qPN?VcBiIh35%bezln0TYfF5h={wcK8_HF^VK` zG%>oDj|0J+b`+xJP@tkzSlPPF2ho9Hju6ZQr$J3u2HQw}j4|Q|o}9UUlu0H~vkQ^E zYduX>+O&T$PPy?hk2{T<?>KeN`nQf)jR2%Nv^E>DM>|;ICjm<8ly|{4fDGJK$b8D= zK)BvZiU)VQS+0t+x-d~Jk)!Qz-DVl<iMc->6a*~^y6Or`u3Dyx$10o>#8SSMQ_qC% zg~V_c#=->>lRxnUoR$M5UsD*SF&WY9`i}XKCWD!fiWHDA+vX&i)zF@k`J~LzHH;$_ zym3Bi_weM66}5BYF)SqDOo5M}iDc(9z2gOG@t<hIxk?~OgIPIiNr!||J+Sd_SHEI4 zEvL47f1@}iM{*NopsF_GAJ{WJ2O4Oa4CngMun)+*dcCf!WaBYdd+Z1fN#I2;rCk+* zE4iSDHQMq-nQDnQxqKjrWkO!Ed*fMQi)D^rq2G5e^pTcJo1}iqD0;?%<lk(Rj5I&7 zy>iUE&YM?I$aqcm?=dLkt?o$6^zpAK>*w^i`~u{M*b#ld--B5d6foK}XC0C_xDTOv z3^G~6k)8d5uXUUGCvogLtL(rg^`tstPmYag_o=4K;xmU{(9??YCWcP1fYo5HOCGxB zyI6On-eB40_F-9UPKSZTks-JXn?|^sb=!-#SK&baEb=JtqX8FJr=<OVlfwHBIp7QZ z8o;XLGt7?;XngJu6RwHbsItIPfO6l=;33p=Dr#5WCW|OGWhr}$ZE<C3|9vjzHh~Yq z`(5~em}Vsz&Y{=mq6ieG9$I<~%p&>E3Ex+jfC{_S9*o<TpYYoMl3;t)!c}?o6TW)K z_rISavHZ^|lD3W0`Z(IxcG?$&?Uf8L1WcFo;c#0ZoKG6|i0En^zYD5Hply%{>E7Vw zL(lWYnN4ss1BF}$*`$scV#kTi%>Bh$L%W^tcfB)C3<)IO4!5OLz^XN(-PsJTozJ3< zIKn^EhTHcJ{%^qLC_FK8=8K+4Cxvw{3q6k|UB3Ba@djK8OnsxU0YC<n{{f%K;EcZG zYao!BbW0o}=&y-le24VLZTjsdyP!pL57F;Y@0-9Pyx;)*Nfabd-*&wKEkvTTEx%S* zu;UOIGc+a(yZ$6zQt>HO0U>0b_~VY2<$k?5hSwGcMh;8Mso_Hl@PcU&-!4lwI=mQ- zxda&bMAL&shpmr|Ag{1`yX)<2#1$JIJk}b#>*}@EB8M|TF&P!_5TfV+%cAMQaVaM@ zYe#V3GdIH5?qohV4lOjVauO@0vR^14`Lq|(CC|rvj%jj4z<L6y=0sBA9Zre3tm&{n zg?T6O?Zaxy<$_TM+sSiRo3x>kdtFI&LwEtDEuWn8ZOjOX^OvO97i=8ME3$W#@9l%e zNgjnn_?WLpERMLW6$e0mcwhk?Twu2&4@lESGod5Qe&_fS%-};y3J0Ie`lo$89rl_G zpYB(}{j#y3d3zJL!;OjH2C9aJ?HYv_P`3W>Bv-yp4(^=7eXcUXjQ;R30!y464W0eL zW2t~iL#Eui07W;VUIZ^Xg#mgw?GcQGj^_h(>3{H_o;z0(S;A%BhzQ37tWPVyO^O|v z7+bX?*37K-d`<bKKgv75Um%8jULovHl=~rCfUJC?MRk~>ZX!D%X=n6gFZAV*G4GiF z#q_EM#$1J+%kYynE1B*5L8#ex>9IshKFVyZ2<veyw!W!C`TF$m`b#f^G4aP|Npxz~ z9YFqdMmc(u<89SgeEExXZJ2^|Z#WFU^20B#Xx99}e{glMHtyz7CO3fA9_2I<Ne2iQ zS43VQ*XWzsdmcd;S+^<$UlIsGnW(dA%LQ8GY%OVr1AJMpgUt@$vP-Y-;Po<+@+7{` zB%gL8MwF#(Mm0ev-}piBM(sXca;MbrNLxD=EdDnq2J?F}zSli87_AugVl<>{4<(IM zdIZbW!t*`?*i)`^04IWdchEJAy&=#`4Z|=Md>+K5`Mrt=OZr<<2rAcv^YacWP4am8 zLtJL~py_~#$lF1}EZ+A9GTPhs_3m|~@M|{?XKnkfFI((<SKFEr9Plpc_!869kUs*O zz0~t;1OBwS=J7V=dMe`86?OE@oa=gXnGaR(TdXkL#!0ZpVz1UX?BzZzgOaFzhtiv! z^;b@Z8+I=Cc(lO#r}h7s@^Y;{6Q%nPg*$BrqS;>O<4*O6MPMja*pdL~9b^^tGIkjn zFWCzO{Pr8Mbo}^B78mfVb%*_AMS^eV13p9O*5RNohsTb<336>FZpYptv|FyMChCq- zo?r{`>3&^8u{*2c1>MNsDyPL{U%Z^~@_bm2f8?OToGSlD*7M3928ESZ5)bm0QA||7 z*`<RTa5#9JVA`kbLwiZn2Ex)wvCB1m)X=d3g~95OY*t4OQt`uwUsLm!HiHA`ES+N4 z5bP=JY7tE>T=A6fA!e69I7hb2{bfz{E0ySrB4Nz|M6r1v!|zo~hlURFYZUOyC#xrI z{NY(K>vw{*<tq}W_-oyWa0B6eO2EA&Tho^R@(u995F8&+mo<&@AZ0QC;8i2w-=C_E zJ`^1=fe9N=;kHGaBa_V%rt<XWqP2cr{%?36GdA-|z#7%F>-WA`PuL}B+b8zB!|%`G zO<VlZ%avNK*+HJarj=<M{;~sSF(I_)oxZ$<ygiNd8&{nkOSXR-qcuWDye%ViOd{-i zmat#2E+q5ee%(fAuWoB@*9_dUlTq_KYv9Ma762Yn#mfl}hja(PL=y_g++z8xbT<3O zyKys{$)UEbe{%pkb^ys61)*#CxK+*n5&y!HoKK7)m)AbJI%%$@y#SKy#+tJW0Sx~A zZ1sJq;7SNJZZP&+Z12Qs%I)LF-hQIju8F<`lyK@8x-+u@--T6okwoB0wDd9HzqYWl z<gW3ecWoO6Zr}-9{dLq#X-t4ccKfDjwUF%9w7nVd@!=I*C~!U~E@8K24E9}$+IT{( zMGq1Qc!C)?IIaTUgd?@;PMTK6a^qqglT`5VkVp1xUssluMRy785tJ?}Oz!2)ptC41 zO%2&*exVrgK#mI`j4+)+bWu%KSpz~3y+*O{C?aAWfK^si6;&b?4*7OVyS<cYc26rW zE-S9YF0YITABT~>q(%k0ePGcDFN>gREgg+Fmw>J`wN*BiHsIUfHH>kq(7P(Gc>+gt zdn&`P_G?IW4-Onj<(bX+UD|XvXx7W!0f>R9B2qMarj?O<x)injGacGPqIk1Nb9<5` z53Cb?ktA<dS9h|;tKolI?d4$?9A&1$2x)y~8<m{jdv3HJ{UOeDYt&GPTs0sKdMnOI ziWc7)i)_L4xF3@znQwdwO}%v|#HuFjEx9yG$_g%i7Ov`woUx$&8+4naiNquYlZRY& zLy+nykVTecuR3-liIGyS-7JCaZyDWz`Ik3TE^9lz(w9H+;7S+$t!`b5=^1aE*?6%r z2%|Jl)lg^*lzPnHgn<VfSwDn}p@@D{%|Zv=c{uQG7)F*Qzl;sbwcEy%AiJBkIc1eq zR}$5WF|oP3tQQ>?G`wvZMmS+cQ8&Pm@AL^vvHKk8_3ya{)5;7~-Uk&LjdMR$Zu@(F zGN)ZYYkZz)42@-$v+rb(OQ9jJa+g&tB^03$k@0#_KeF;wu(klx^E~yQ*P-<Kn4j7w zGAQd4O|2Qcjv_2C4qem4F)BN?<%<vV(>e^T+ng0gMuPo6@B369WQ=WxU8zf+P|=FD zStI`Nk0bbk2HF=K>(QZX*z6EoixWH1!L~#uh#dYG0QC;`?19?&i#AMPzut{5>aWvi zxTC5vthJrr!dZ(ZCKo>7f_;0=_Y+_vQqAEf6X{#fpn3Y-rTWVgGukmKuwKELT0Jpt z+i#8lzu|674-~2&QuvYkxeMLc!2me+yZ7MzG7jl&>7&ka?U2cG#f-LH@?gR8HF0)J zw>ol+R!#{Ib9T2Q^mQShU6;yLLGJN2aSr<*DvXEHPsZ%Ko=G2Xi1&#k9!p<3PPqb` zJD4ZpQ8eQV*!Tn*y(MXr*zi`8Jh};i9ljnLsTe?$HLbAYLDg2Gl4VACu9Cov>&$Qy ztN>Dq3V$^uB)HoI*1@Vbh>&#kg^1QKqE4E~^U{d}CJ-?}!Gdn}N@$-%U`jPW=_SN3 zDZCif%g5ebt-OL}d`!J53_S$caWpQ=cYlAZKhfSQsPoXmoYncZSp-HD;*5GLP~Za` z<$2Wz&>{9bd6wM%z_{v)8!)gtXgaMi@O*i>e{{F0sht<AworfGd1-GT{5<~iwY`o2 z7^`=G;7%cN8-9i$b$bI{eihgQG_JF^JVT0+F?)P89v&6gqt@_taZog2&he0@tX_s_ z%hF5G5c^D6tN(YVXdxkkm+D!?=h|Y!m+IaAb{})h?w@HNg$HhQ`+}Q3dx7-+XF>hc zt+hYv8vYr3`q@Y1Lf%Vv_wo!VMpSCAhE2&6t~Gv=`E^!v3)MH&V8=s4PGpB}!j-jk zwOwGVQ=z9<J0-LAw1>a@zpr(D6h81*c0M8gn@E0v8m&G1k<Si4LK*A-izfZQp`@(; z5y_Eix=!m{h~C$ep9tNTfw`n#AU}j}K&w6ovw+gYM(+aN2xR4OGf%y+u<z&6zbF&? z)<@9DqOAt;4m0)c&o}hoark)Dy;cNRRS4)v?@qDXW(2*4sUb9l?(NUVJ=fzRED}&$ zGlprU;PxR4d@#-AO>?a?2!=Z)Rw2GV%5CHF7-Bd(w$Xp<@QU_Ri*U*jJgA_1p2BjS z4j@M19epHs^(sTg8N#r2-`I1ts3JX!kg#2m^Rl3cg}9ewvOM@tf<pSxWAFhT+T=IL zsmI^r#;XvzxjcxCqIDSc>K+Pza}>yJp%N*>^AawF+vh>YA%hKGkNaCLZe0Iz`x%r@ zzV9LpN#}m(Tbw^ePS|#AShPH5`wRiS?SA1~F<P^93t~IH4n?m_6q~a7s{w0#6844z zS4IP8&vL;yf*p)AI5&7(q<fT@xspKBJOt+|Lh*-~;}~y#_AAW1)dYQ)@AD6R5rVg` zcl`vA6^O(^+NbA~Nk(-@^bTqI3O<1KhPMzNK3Fg!K&|{!Bb}rW38P4`X00!d_BJFR z&1})`Mg0t6?xYiv21k?hDIpkfB}y&(EwwyX!<}WNNl@vCd-+`njg<tzAc{3?x6{<5 z&88o5rTuFVgI!qx1+Sb0^@&sx+N4g(+0R*{+NCyKP^Jn{=v3-xd|En*U&zp$2(hBy zDjA!_W8VZ~a-C9pz9+Z|J)0%MRZaw}sxU2ivzG~BZzg{>Y5}rFQ;F^?KV$PuI+4mf zD$yTLoDd@n{##AH#84r>rwjc)s+-)bA{_okgf}1CwYuF4^I1|io72f2Vz9pt_Ud&G zA@%KIy@Zh8!#@{ReqIj%BKPDXyKl31Q!dTRn+$)ghGmds*mKfQMU8+Lxk{vZh19EH zI>pr{Tp!-K*`RFM<NGbx>htu0LFlq}%jXqrEt7M_!}DFalX^h8E*O3`w-LIHV9hrX z>$S}-8CpN-Iej8Nq<?lvnAUfSg?u%&a3GMw3Xb#c-ZCp~>hD_z-ez@b>A)L0T}AS| z^()@8vvBdg)w*=)sn{!9)!=&?wgy~5ooO(B{U-Jyx{#2(V0a*ZuN7t_eGSgcns>d@ zE<D<zna{_%dDvW+#aO!LVY*zP0@r(Ys4~?=EP{m>8JBb)U8|P_pAUM1vY>PhcVUO; z^MQ~4+#bOJjJY0S+<5Hjp6T9c^2+XaxsoPXRBK2-E;sO(i&Rwgh@Mi**C*O5z3{%x zge=*w76el_2wfTuEaZ~EOT7&w(P_dA@!&zP<cO?E{DsxO8oUGCx#ma(vr#S`+*!7D zEA?NWQ`A0fJKquhTY*p+B)z-)DIF3R|9gR8`%i%w)Uc92kU;<Jp8tYCpqAAKlk39g zm5rGvlp#h6v6*MNw@8EnuNw|)h|bH<z4XmdcD+ap!+K0Xf(+d+Z?AE>Zfp5?DyfNJ z@o;|S8cQDG#ZJ&WOm4Vf)7-{?{vF;yyb$5z@jT<(i6_4jJn?U>-hRRmZ*TP{BP|1C z=uEb!Cwpqkuss@MjAmweO&4jVUX;J1L)x_ZJ5|6f2Wk8*!lM2{)okaGyQCpWTj)wR znZ9_4Yx?dzDm20Rsn$;mlCV6d!&$3$^N~lhO4`D1xdJeW_pH{Y-SIK^xZTy2F0XFe z^%^-X7wy@OA)BqXs!I2me{T$^4lhBKSE1*Uop-*EGAMZ0vR>h%P59>Fpg-`vhTALf z$HvWqT?51slw6EvB73tKLg%;=$nR8q3xCcQSvWgvcE}{Ncb%?_FH;diwdkPaM;&+h zSgq7#b%?Z{J2`4kPT24i_fzGkHbpX7_|KIbY`ej-M|*}uj_`X4;qLd<OH`XKECws_ zwI2HDb>`9{5`Sw=y0gJ}C1?>aBq{Q+6gR#?AWzH&omn3-hE*ZS%#(5L0mhe4Mj$3U zTMq-Jgvv@R>dOtg?%fKgHb3X86299VTsH{E%!W^FQYB<|QHZ!-8L(rjAx_>sH}E|F z`MerCB0@h;4A4y7MJ0XRtO34WOKyFL4|aFo-}lAE4h`;4cM!%dVwf(y*pflu$NF5m zRJ8d*mILvkQWika!rz39&CRD7v|r9BF}md=@KZE{42SgW1OaPnLu03=Ik?L?2YpM% z54VO4zc2A|ue#O-dfaM?>>nV*$a_i#P}v}CzZ^N+QzLE3a%SkBv?2Ifomt1!#ws_x zvMPN$aEBUR_d9%{DA$h|Ce`HK6N;u<%za%E-Lb=lS*Jt>X#SYEOF}rH!YMc(^|>?} z1Z;4?4!%B&9T~N}Y%Ps~iE^}eI5q5#GpBKDZ@1Ur)CAC8Zxu-I1)aogEyY~X1%ojx zpeb4X{XAiLpYL)6AI9C<?r?d&W246&n(S~<wYcXwILB+eXMEr9v4QcOU|d;v$YTmu z8{_$#vf%l=CCT>*Qcv%m&%eg=K+QerkY*O4nj~5j6hXrUuF7AiY$t=?jsq(2x}qu% z)yhztD+;kg)Y5a?bo5})H&w!XcsqRGfFlp3osyHHuWas_y^|pX&1?)KAd~Z%IOxo| z_O@=ZFhi%^O5}ScI#bm)>ZN*JJS2Jy$UlEw;(4Ec_2SgE$@Vn0I8gYT9Z@NR)9d-Y zua`rI!V-NkCtE*mh&^aGP|Rou>ys3h%N7n^StK)%V)A8HVSStX0P(B8Di!7jx} z8N3J0`*)+ES%jXVKnf8GOUcfttIjzw;4lJ=*CuwxGC}j-SSE5mD$TMQPGhlxrV$et z!pUZb2?ur3QeTB<0;&$9n+fMgj@Xg&>SSX+^ce+vKr;`%N?Z&SSHYeTA~hISQ9ng} z(n6|mP;xpHS!L4=KkVLn6agGB*pyN~sT>PgQdK5d2qcre!X`90gAn5Y7mEFvF=BOQ zA~lOCwhxn`L0Pg$qKh8(s@<SfggKB%aJTGbi{Rj2g8o9vM#A*>xKZeN$0B&B2Z~;C z+RkOF#+F~9ii0S`Nn~PJvhBe6iIBpqfkr{iziO}vDBFIyfz6%7E`}j2AuFQIBT#pe z7K!~sqNNd?h7yAF?n5Vv+XB%HmL-i?;e58-4TZO-LHey*h)Leu;DL%%YDTR%6)KIv zwkXm2?@vB#U{BUqxJUu&n%rk|`Tn_|%I>TNMRgx81^qz+7pgLOxI!tlWou)%ny`_j zb*x|jPAzbvdt*^kUM^Wj9YV7QqY_fJyJxIN)mt1lV%;j@FgQVcVw&0Zq5<QuWgqX3 z<$~N=yHDYMriUrh<R1zx33f(<Utl9x3%yc})g)*lG{3r_<Vfg<ClfR<*VfVD^x{+q z>tO9K0wB2(g-kHZc#yjNV)|G?xhYFZPtYa1wSdd;oZVy~r9epsIYy33kEHRC{%~gw z7`!Y%OMt^i#g%UgTNN$Ui^jsSAX7#SM<$;y1uItN_Q@huO(<9-30*5>ErEjXstcl~ zw(17BJo-(=N=Oi$g7!5PvNY$qI5jHyhm31j^?R}!Kfp85ET2-PD^-VzOmf8uLI7<^ zkmR#A4*{{V%xlP#k5fsJTo3=+lC}4bQZw*4>;DBN9Cb~OZnBz9mrkPCT?+=pbn28B z3ym{NOhylDZf;{g>MR_{RuK!D2!&`tEvpBI-2e&~m9&(~`w=-1YpqKIat}8w+-XCQ zd&_{!%A}>lLJ}4yC+Tmy!%CRcc`A+zZmo5hPsUQLh)@a{*?7uB>t$u^Wd$ZrC|C;Q zy2t9kEThRlZTYJLGs?!w+BSUHljcSW@*X#yDe=y7bQRB7q7t=}#T*fRfklZH)(Q_% zzzoSV97C`i8}?jI&j{43?rOr{F)}VMmW)7*u)0+G0Z4=oRw6j6M3k6>%wXr4$LSa3 zO0oT)-XN^N;SkDZ7#tZ3vD{E3*LR^B;Ctu+4s05;RBjN;nh-d+>y{ly{)+1YmTn91 zhLA&gI+Klpp5|yZ4#dttuv>i)!I|EoLO3(Hpc$J&_+o@1q)UbnR$ob93$P6;fB1V4 z@4e;FtDrg=r@3_2Z#9Buj1QmJX-s;~vz755L*dO|Uy%PM6yhNUs_cLMB2cLRccH-k zAEB_RW$|O$ME>kXddFwk4oxzisA@;3xdr>>AOd6>#}o2n*)$I{4ALTm!hb)1R>fCB zqcZMuOHCWn&$ueDEcbHnoSZ+;>Di97%vk_fr$)7$u@F9Las4(<`EhHieY$=Dipo}e zeD0rny7;bUjsec(I5P{kUTOo4x7HOtygBlEO&dREO^<~fQucv9mJE}e<1vB`3$q+P zI0%dPd&K^YNQLEY0K&+l;Z+4-{K7gun($eftqU+?Cdt-CC3;~RxPe0hLnzWz0x0Dl zaWz{R7^6vfvSb4w%vZe`r%%rVP8z?CU7p+dCC!d{ng+J2+ACK)cE~WB_@`{iAJ^yX zGhI)1#M}ayui(TBj_kS~&b@9oGFG?&<|^E4<vH*pCy(p&a`I|elC4MSh&GL-E8BKU zF)`<t`ZuD^3~Jg~9|mrmJ=5X|gV=o}x-^Jb-m&mC)h9>x7Ga5r*iujj;dP@g1=h8z zT#`X1iDO4pOF`?~YP>rmPVo}oBtsAT3?v3>OtdK#ZwnDoTUkB4-PlD#w%&D~1$%`G z<ZbV*IupKL$<ra<vM~)4p(GGTa%|(puSDOmsJ{B69P-z>PMd4KJ>Jb5qu=inPl^X? zhA$1vYhN27d9=^6=~Kl$xi~KCIRmE#_3-x{AQlXpShIR;vNgjgM~c_FzMc$zj3+pz zGg=xef0oDGd+WE(C+NR!l8h1r+PN3M8htqje{mgrjVOA|*giMMv}fs;Z>$oRxbhjb zZN(I1&Dq~nlw|!a$x6^W>3rV1DMYGVdgt1i)z=kBH!aX~O!j_7ctq12rJw<jN>Y%| z_bQjy;7-}QIy#tr%~K;`2G4~k4}f(>q7U1*uGq&J{^gK`pd+%0Q*+4N+7|q=m$s)& zeas1-*nj7$^F~BMM*(>vcx4)hdeL{^P1RJ&5j*0yd0;Y<plm8xPzq-fDq&>bxcrVn zU-iUf$WQ;1d1G7K3&XF+LEbO8^ZRLfCyVticFxiF4~ur=V?0|9MqpQ~=i+y=T+Bj7 zj0F;Wz-~a+r`GWIW>J^>mNOXY0MG)hy-OW_F%zASu&aOFl1E>|`rfNr)~;$2_4sHq z;%Zj@q}Butnu-&@o*FbsCbDEIAvXWui|NZtElfjXkUCG4h<XZ^^vHusy*KyF(*n^4 zxFt`PDSHAD@KLjU{1W23t*y9s%Ol&OQ%$tFh&aNuM3NF}-a0Z-klz=FXgEDnMMF9h zMxRqzuuq<6VCH4T(@Yt`(*bHwtvH3rCgu<)qY5?5j;^92-{b<)k*^Q!ZD=wLd!qJ< zi4gP@h*c@i6c;lgDyrDRw-rAM5;*Vr%<t*f-$&YlUth9)CAwm^s6c{{e|3Pv9d2<L zyLMqK7~kVO+G9+1+q||{eKu^I1)cALmaO^2f6(f8!Jc*Z)#p7FhUx-g(|>!C{9pFK zz6?2^_aw53?~lrWlfe5ei4SGIf6RUrceHC>(|WZS5-So*5CapL-;M2E4OdSNC3C#1 ze3u*^Ds{Q0=W7k74Wxa3pKeZdu3d-yRjrlGcch;H?$6t=Q?~fY&P<nNx%#zP#^&?_ zb6bzNhJ90~t(+%_Qc$UZwhTbb+d;*^mX0Xk<!Db*CR6)(>b$S85bBaWt0v*=Wgh8f zeVU@9OC)%U?pKvsb$PkYy?RfTqwb$Cmzo9V?9m+b(6@AO;+nfFF6we)8pgZe!+4lt zhd+7em6ga!)+C`Y!JA5h^{{XefBuNI#R*7^+TmYZ6ZYwrM75%Og`D43;cFxL-gJNk z1%Q+tsrP}(d4ebXRlUn+p*zlFkfjM-vY@}27QM^_z1dRYrg8D@vFs=v2^aEhqI-rp zyiUCJ4fEjA+II`H{V7HBuMgVZMO2()I#_==Vy=_P(?HptIz6+z!}#!@=R|~&&`^Jo zul3j|9$d@4t52kuG&yH_%Xw<nm1v;px1vksiL5A5S7AiaoJ{=OH7-n1G<KvSvx)5x zU9j!^QHXCXP{RqUvnf}swlGPZQ&YlIIJP8Sv{9mpdq%CS882_wI$EpX=5+UcYSy+P z6|(p|NTe1|mMUda$acAu7{f&QY7_<9FQw<ygGNo|f(+S^E-gQMwE3dIY-->tm+<H- zmE)u0R}XBq>{KNl`WwJU32h+Vgs6kKLG%)c^a9a^CD~|5a1`lumd$=od}tUBvOoz9 z;?4(WBCE3MU620S)i@|{$(S$0y61<1M;uIxa!&1R0Zg%8XJ&;)D5$HcCD30~7Y;Rm z8cv@V;9(3L(XR+rg8LW_d2uSK;C~m)<OfPFnj;j3j7ov2fk-!ZK^hKPRp>?^DL6%o z3%>(ONw;Cp5_Z~=`YK0F+yu9zf+bt&M;3)T$0lvzX@1z(qB)uH8$uIN7vw-GE{h3< zlxa^&ReDY-5=-4eHIs6<Ec#MK0XK;d6WLF^z=c<`u>+-6DxX9QVI1S0WI2h!xrv19 zPY^Px0dv@VCXzF=e>`b16PwQ8-iM&s0Q)=!n$725&CPRCtsSezc^%0ZK;^`KjsYFC z{z3ls`mG?kf0{MQ&HJDviszxLqc`yI!>F`ANP&hFX%PpL5@ON&hyqZBJoZK5seqh} zSD%SK$f#{vXSD=m!4wRTkml$XPqPV`#GruEB?zE0sg1y}V>AlJv(R$SEfX|k?^x*C z1Y!=>=LL-#SyVJ(W>CO}+_Do<1i>JKiNu(|FnHd5I_24rqP$R&I?;)7`XCRb_vC)* z>N<ZmMu{mFvDk_t;c?C}NmRo`&hoJR!unk&F7?}fS~Q%^&0@>S9)#vl@RKE0@}Wxb zVz%F8w+kG7dIFUh<Wt60w?!FL${6hGDxk_h$qr0Y<sl?~MM%u(FCq4J&;~)o7j-pf zX%x8BWkY!`Qde(eKs+WqwgntC@Sjm+CW0OrZ+V2Y0wM+(qte<%((ZV@q=&LQCf``U zYlGCDYuQZOgd{}YuC2|ESq}jtQXE|Ieu%9T_*v(Z;gS4tFZ3E6T<{6wy(uAZiq)DS zgiwn8SRk0EyAu$_m6n#lIx@^Q4-XMVeg9nTX>cR^DoCYi(t!a(OpP+voaEjuN(}&+ zpKa}I$a&=mv)bzy0T14agMozPjofc~@xDRCw#fQP0ug@RY-)+5CGw<dg7H8zR0VZ} zHSqEbp!P8-BoQN+Dsh&10gV+ST3nV<vm|jPaMIjHRcIQ#Ot!N)Sd&E}oloGQ;3biR z0Z5sEPQ;Y2Sze(j=w(BljJ(jgY1KJ(As?0KGQuG{u(e<L*9o1!3p84QTN)sdN7tf5 z?XilO!~z3pULvpQ&`iyR6cjk9`c?_XYB?<3pCJzM;+C<!kS8%7QJsg&;Mi+Rtyxc* zwS+hc+A28LWm#N@f?dZDb7}?@6BS_(X`V+-Z9|`%g0GM+-R6G9U+*8Y;|koQe00AV zLp*_RVvbOPEtJl8HL8;G7ods0Mjdy&@5a`g;{$2Wx<WSNSaE+4UaFdt=C|&2&2@B+ zSe^VeZQZG;UQ)vk%CYvo|L?s;f337m-w=Obf42X-Oy&5GOuf+kRNmmg{`xe%@*RCL zrJI$FB9X4|RBspdS2Bg(Aht&EX&?KA!GvQ-26gHB{pM+`-JD1P4~D_iQp^2$^Y20H zdgR_m();rse`R1f#h3@Hu;7vnPNE|z@(nPg@7%W4o#o?Gv|g37^YLj{6XTI!!L%(a zzF^Ff)p)aRUcc3yE@$VnzMlS26Mw!<()#;h1VHl11NR1aRC{~9&;3~<mi`T`7{x~W z6`a`R$5zg;lU)h>803ME+{j87fJ9;>WeCyJ;`<4E!4Q$CWq7QX9lR}}qHl@>=dxr> zwkF5A#cp_v1j|=)-A?asjR*T!;Y*iGYs(h;<+8&y*Q-m%C*Z*0JeOUz`>DH0`(MJ= z#u!T%-PPPj+2zx8PRl`I(~V-KYKP0)<*$a{kqa9cuIB^#iOwZEA*}(dp6r<L6+KyG zzA_U%UG7~Ou>YzKE}usFPAj+G3RifOrD7T2iG_&JZe3xVf8#Z^Wd56J_@^nbpGq-p zL}olHJk#)vr6W^Q|4*`aAXY>~Qj#-D=-Bhao2j)k9eqX`eI$|ziFz6=ig@M*f8Ixu z3koxd2=$0%vUAp>WosJJT@u8=SMtl_i;)D9GKRFwwCT?bRED>&nKP%1qvfT+4jcsg zt7H7y#E{O@QGzkk3p1w_JrD7vuYc|3C)*|-{96wO3ZUlo=-}%X@U;f;g^yupyX=$u zPTh78Yy^o~*dyVP?D<NWj)_x#3kPE%5kmT5O%yh7Ty6BdPQJ5ksE)b~1}iZ^lNQCQ zb^5;83P<d3>t{YRLE9wAeershT(is7AbUG8cRcuf-|T*twH>=#I=KSl5*nr|%~<+; zG$_xsQcqunla#~<BSjD<B1cd(0QyL3IQHAA<F<=Nlzl7);j{@NOnf}uJf0@T#K;ox zKF?oG|GDbTO{KgJBpX@{)Mdag)R9`0TtCM=A^2=@u5FBdy;z(K3`gAcVc_l4^>p9Y zu?PEyxPLZog`h#SR0)O-(As2f7abfrI`8>*raP7F{G&5IoSG{CS7$b5GMS#l)!<q~ z*S#J?PpIU((AWBxvG?P>VB2oM7`X1*mCyM(D~-i~y8A8fnHCG5bft$h1dpv`{_8rp zX@rP-eM2@xfpR6&*@<)^`FA+?`1ddCfU_34EUT&k=BdW<RSZ`ef}mbZC-h*X7~@LS zwdNSHY$*Ss=)%yS+fbD}8w?t5m<LpzX!FWO-cckm7%9qCT(Li44AHr)lfS{x2aL2F zmW(u6sn88gs<x&tS$HKP-L%;Gq8H9HaJ`tyc;&QXr_MJF<WXvahqPJ^7aI(p#fTgD z&_LQ+&1ho>ZXb1!k2R5h3kB)JsFIk_l+|n6kx)eMxz@&_cy+#1jLM&CR;nJ2=&2OQ zpVtU>_My#YZxU-`iWA}+8#F^3MTPd&t*>hiU|S@>F=unz+?#rc=4l=t@3p(N)!jFD z%mtUEal&R~&_{jW6?E6VORsdtNoRrvn0f9%TY35-e0F*~e_g#|_0lzbhk^mo;Z!fY z%}fA#BIl4Y=+|N{#NCSQleQ7)n7$hpczFR=xR-X$JJ(*DD_3Q=LhRi1de%Q_+nxB@ z4bxU9Fw>8bV#I-7Qtyvj+o$?&=YljasdDid&P=nvouoTP(HLHbj?3Av9`_>A0+?a0 z@I4A92D+@n;^!&`jCSlj(%Ea?*@Mk$ABV$5kfQ94o=*yNTHB*UiyrfIpQ$8^{a^Bo zi@QogLtv|yp<=NL3}bjD*c`7U!7?4kG$oP_L*4f>HLfDCh7^?C1Vx~vNLs`vPbfnY z6BS=nLW`hOsZhHKrTceT%VGzm22tq}d5dw}83Hgfl#qz$i;)~QX@vjiGRTl9hL6<w z9gOIf(pflcw-_NzR9Mns(iKE|t(g)>P^nPhXTqRn51@?bv*Kk>Y6Fwlq&I@FGG(1- z>sW$IkZ{0JIx`tEh5VWV)O_Tj+Lxp0IuH?+W>r}|H$a9^nL&<#DF~t_Fn}gTC=5lL zS$9_!hWFhOFwBonW$l(K!YH&TkW$820e^W%uxKQAe=Oo4)Nfju!r(4wS$;R9)sm{w zPlT6+UfYooT(_l-(u@u$t=gj}r-zxBrR7F4=h6so$KNyDQ5dVFK@AD{<Klyd-;e4! zZ^$6hNs7e%iWE9zEA^EKoFvmt`?f^@q{0#TUD1G~MGgl+@!3t$Fwqi)l{(--!bnmS z?&n2fk&oBKg$Rk?Z#9w{jNVQ#6wAbG5a1gtKZ+C|0IK@=odu6TtoF!6s9T1{?6Yw@ zw+QgYCH^Va0mwAJnE-OsAbb+@=f+%LAzW)RA$J^0=Zct~6Sq8atTWy#9qIt}>qkX| zBW4nntokX3^uFos*1wutG)yJpfRsa)DT}*9Ae%P&hheoNB8Ivgru960C#L8(?Ma#L zMXf5Au@l$M7}b*}lXA68?$Rc`At_@KdL1bF<x#z<A*=1)O+!8-lNlSZ<X>hJ=-nO< zn{S&``8(qZb996QL^W7?`Npu7h*IU4svySCmrbL*8Zcp)_Q<M3AnZ0-qZ-NQngWUs zs-@gS_%e`Z?cIbh=nyiR{P4|fepq?6hy@?8lOo>!9J4Iqrr>Ak8W}Px3%}CKQ$-bV zrBDTk&O~lP`nh^5wRwz?H|HP!5Ul(Bnk;|f`lI!*43PueKr=@RI>~hqUva_IVDm!+ zsPJ}cTVDh&YtY@np~L5SR_-Si4ek-_garp4CVZYiBF(QTf=@x@RpW;T(?|ncgJjDP z7F1@~8KdT9>WLel24%2Kx9x^WV8SVdvO=Lj$6e{1O@_FkAeeF3#c&2@A%7Y<RK6Q4 zVEkg+DA)$)#wrL8f@B3p1@mwH>{gsJyU4WJbBAX$^R&vYyT4l1G}7*7cdce|5wMh5 zc@CNWC-+%63gxs(;Fu3>iRGyoBh_flIfP#rMKCj4mZ0L_kv=o4$<kmo^ZolWi_6pe zIhXHW){Mc4G&yLeW%Xi8NfGs4%1lX@?`qrp;>>KCs`XWR!IV?e>NRstW6>#^C8BhS zI;ZSL-()og4DUc{&}eWz0oK2|-E4w6%aGc1ct|Iu%Cs4E5*s8Gafe8F@Zc;FiIDYt zD=sV<jel49XCs1c<X6tP6fDW_AvDBYr_)<Vr3(2mS;M$+zZbC<XHN#tZKD>VxRUPE zNNK0vXRtP+FlH1_zO@&zf)t1;xSOf+aFEe#wK=1HQ;N=A&nP+w`X(&YsR&pBW%Lmr zCMh7zd&kPDo0Q2hZNS&oVx9(WJmyWOK-z|Kq2?GKc7usZmxn9hJXn{;F=RBb9v%FV z-*%N%TvL{Uo7L&>Lwy)`XzV)MvRWDqX^Dewo97N>I$l>y-=iH2Fg!2GXLJn{lDbfo z+P2Kr9(vCzU$lKH?Vh6JlziQKEqOZ5S)ZXZ!S4{{uBu%idj0mNPQgPKiFcWG@!#;@ zr#MU63HBU4JoC)JdR;S5GtwW1b3pl~{oP<Ysx|Gl@7u_+h0-Nux=Oi;MMImq_rnfh zAQC!{x2>2qhNKSD9VI2MO_?+W>w-Z+kA_em<<_AEJ@hK#7f?sF9L3d61S{WSuxeJp z*_!QOoIOp_<UM*}C*@vNW3L5{NNj{T70JVZ^>n|TTaP^$pi-k<fVp-%UDD>zp_L}W zmu|JqPB|@1@<Z7dB`VK2kv_ADFyiRHGLVh3@W3U_6JoOFWDJqOp?r3-uVK)iNM+<! z*^+4Rs)eaId#}p_{zst4eTF+Is_JM9nlcjZ7L01byZG?X4gX*{EC*g6q|%q!eL8}q zgq%+XjZyvLFCi5biLG>NrUG|u*o^_dAE`*5x~kTBwvOCL#yC9N6&^JSouO}?ofr`T z$D7U=&3xv#auRK*Y*nhCuTT=%MyIuIgtM4?n!}n)nCZITFSbUH6<|&2jw%<JaXQxD zY=5szBx3T1RZN1xt8JrEpJh@J+sdqVt*9jm`LTT+sjnF!SWOg(aL~Sd&~y)N9=K@a zWW%G=e9x+|_e5>whpMWDi!n*C&;@qykT<5sQS2D~S1T4WCz1w~{8}Goq&x{M=e%?O ztYGGAnyf|aw^z5Zu)*#Y(mi9aFteP97Fm5Q)$mir{KCJOKfS_TGOI=eY#J;gRTM8g z!{UElbQsE~w}W{h*u1J70zLh*D&i?M)aa1Z+b$c2D-F#ep)L`bRs~FGaG+6E%}0=^ z4bGvSqFyen{kjW01|NaxhPLq<i3eV!t7Ba2#n~R#USW=%U$Ng1Re>5F#Ro?V2IVTv zfH;7fS9hK$2yk1iU|jlBuvG`bwcLuY33arydo?(WU4@c;2Q{ahcnI-vYchKv>rhbe zmL<q2yE@j7&4!>_w09ji{87!54i+h4(UQdwJh>dA)4Iv74!`MeVg8j)!KrpU@jZiX z=s6&xr7t<)N`pO*>j@X2sBMGfb<?h#*PsZQ3EnR)Pn-SX=CB?Pf2Y2(HpR~gt6<My zXGaIJ+EZxCylEqXWU4*Dh>GA)hdSb^ap~uSngD-t02w(flOU9aDe8}tl9N$93yRGA zSU5NZi$-n~N}C4(Oa{MezM)V2TzeBngjC`<B-6Xr?%t1J8^W|JJpy1#Uzjt)j~H4E zr8y8R+1&YRiYO;-{CNI+l~CTPIYV76-IMxdhj(&wcsGMKEX(Bx^t5hcOp^W|y52E7 z)23M)or!JRwr$(CZQHhOb7I?0Cbn%%jGcMj?_1B>Ywz_l-PP4+RdtT5@3XtB5Qt^& z#&$%>{+O61&OT{S6S)%*r4c7&u)Xy1=PPjk%9rXnA5>%syhP@lh~y`W#^Z+unk}6m z7w<f<XTniZS(L<~@(pYDg9}C>GJ2;eci}r5z4zc@lO%{K78(ie<pDVEm^#xJ?_^@G zGt!Cu0Rmw<v@^(@B@jrqd<-8p`<U2+>8_Jz6~wh*Acy3q8dB)Em!9og+>5VKc31{W zLuOF}&v1*?jZU#6bGc@>P?Mgg0+dfr|Ll(6936KY94RttC_S2kO0zVa<~FxyUe*Dq zw$f~z-4_7n%?A7ky*ns(0izN_%zWmbLw~RgrQ^032#z2Mq;2eReG_nBwqw3%Q433x zQ6OuE1o-pU*;>syNn<@ty1H(d35z6jhJ53~&Vp3{5hMbX&wfI-y;gs<>mQ7~X;3U^ z`Orc5ie@sleJ3O}#p^ZwH&^V1S3AMnSRo=i6l47Zfll@>x!tma*QWilKPQTsc3IUh z3!MwQX=NIBovm|Vmf}(MAPy}0qj?xNC7loWOjX4*kXX^Ps@OX@*i_ea`ASe?uGH>4 zJ7yC06M#p!e7Ue38HLxoAv3BLBIZZoRQK4kQ!Fg!Q8rURm@DE@SRPf0Hhf}3ALLj> zmr~WpJ*MJq!E$?o99i_DP2qY#Z&vY+_0YjT4U~kG(2l*;KDF5R^@1NT5I#ro_u$5X z4{wQm8d1+M?3FU}XHt3)A~5q~6_gO=USj2=hTHHVFZ6TzbVhY>*Xm3PibzK9^R*cd zm<P0R*LI6ovFu_6ubf+BIg(^K<4V-_N>~JQVXu}Je?RBhQk!)Fj;~lWPLmaOU__?i z`!@t(Ct6aqXjruEhI4Nu-u#5TGZIS_7QhvY+NbO64e$jd7HO0Va$8%?l=Nfb&w*9V zz=u7R$qIuC8YWj23t@ZAzcak2c?@=02c_)waq`SRgH~Jo5Z4)Ks3NA~<4F+_qco7E zlQ~v{^ZEUX*Q){w=rKiw15@f{7jz7m98zY9$MMK7A!xDw!<mQAJgl9haVZpER#*oZ z(**Q*BV{@R@`_jH>B#s0H}CIK1B7V@@{b?8vHv?^!u}t^<hy(|3&N)l;}h<}#^jic zHpY)_PkS4zb$c$vj<yx)L|N8~fx(PK;n<?0sgGArK7<1S4TbtBva3ZKQW+uyH3Qw8 z%^`!AgWMk7SdZL>F{iZHr`O+zA37}EliRQ(xKFRSCb&LguU)=8ALqI`SuiCHRt|Bo zrmJo&m%GikPPM+wnVave9h_aA<29g)*B)-{W2G;l>8|Oz*GITKP_zx3GlEdvl|f1! z%YnPCx?6AiM!Q$<B$$R9urCK5beSG62Zqdp`aUnGe>oHQOPTsPJrIXd)qzZN`nB%^ zn?B>_<(_n-d8<42b6#%;ALiDvlEv$-v0WYBi>GOj<<%L_57yf0*RI6VzS2{wo*JIS z!<NZZwPc^`y$R@H-kgJ6o7b+?0ecsoH->&vwd~`8xw4mK;m5bC&*gbi&{eYq&h_&i zjF|05PumBGQh5Xj-am%;dqgm=+;UDHYuimUKB`((T=63O1YXD_=18JEj<>V}PYsoO z7Q4XRv<C4)@lZw=lvj7Ct2g_LRqZ?q<`qQ{^F(s_QidkasV~OT9XCl)0=!kF@<$<C z_gy~xqlUY|5Ax&d6Q%Guw1G?rQ&kU-3BO+6o<?v*)y1i?A79Me6l+S;zFIRPecm}T zVZJi6kFL|>V|WB>V0^K$qe4=@%1M69-p^MSHbfS-p!Q^CSzFKd(fH(6x8iB2lDwFm zbxr%ewP;}#7CnIOit7f2gPr4!F6eA`uY0n(@)~>YRpP<)6NMY_CLruZyvgR+nyYhR z$<#|$Q+pbedgTm6qH3b5rhhtgJf95Bjon5<-lID%KE4B}2^7&4=PbV8G)S6kFq^gr zGsMN;872#m5mFf_Nx_oT?S;))ha08hsNVh#qb=i8nRvfA*t?XiZGlMNnDgyk--_qt z9labm91?11I%FFaeF+j<8;UHPFWk?4(#5qw_VsS~ayXaw-Yvw|r|tPXTGRv4gpzzU zsf7)Mwn^j<8ltw&&N)uJ*nj5sb!R;OJS29?$FtQ};fBg?N}^||Y&7BS@%X~*@&b>4 zO^+cBd=i}WWx{#q7}p5%?*6)aAI%U)Y(A={1=Xz|Gk5v0G+E(iPyYUbF+8amNqWvS zz$=ZjkRBH&m@C2#VW)lpK=J?aL|roNN^Hq6k8Y~HNFxpMqlZzt7~#h%)<`b*X1b#S zqH@6sHb(>HPkLUUf#ZbCg9>CyF)z5}2dCGNv*?4-dq|v>H8hJZ2<$bWD?!d>NKqaz zAXYKDeB_<F+n)?0ec4+i>ggu$D~(%g=mcHu{A9u~18}KRtEMDLNA6(8NdIFFH#*E+ z&xbN{eVjmMA`XB{0Hm2VD4!(4kYB}$kW_NRnS=|*<@IbDkDIGr<$M@QQ!!WiR?F`{ z@Ru(Mp^hq!bFHWANUjsH(^I#;tkREe91B6G&2AQE>?)aUY;%9+^6G5*y6`&3$}!oc zv$Ex7`J8xqjSS44K1D4aRL6UMypc4vw!O~pvApW)^87^6Q(f^D0{D?iy>9;THnPea zH3!~;TY0_sbR)4#(S_Dc`#zI>AGv(|Rw}A_O#7&dZT__2mcPy!i}S4|=*Fkar0YCu z#<SFv3F9<b@%K6`$o~EuDM%d{UDveD-Km`CB=;o&Ywuv>)S@0kYY?Lf_%v5*4^|X! z4i<`JQnW7{`+ms$JeRkFyP`Wthb(;VV+?4+MN1zl&{oIcQ{+!-txX0h>h5+;wKeRu zY+d8kNywhcba7PVxwyVXnx71YOc(iqNQ9|5LY7FC#FJ49-+7CI9Cj?CXo6S`m6Fr~ zSL%kUoi|}<+EW5`K`^x8dd9R{HAj$#IFfa|&^=dR-mwd#9{s<V{DJyX_NjWNO%iFv zIHq1cP1|&06->%GvJNwTZX6%0zSv$I@Bd^_1XHdG8dd+Sl^wOjk^#ZZq^(RlVEW3^ zJ|LE^a?K8s&kG<*utz9Sfz!X>!wwE35=!GIS{j6qI>@JO(DME$3xS(1O%}5$8;qT2 z3J@9Ns4Twp&mVMaU`XaqXwM6BM9Y@j14k!xQj8Y^fPWbcv;Qn2RU#-<>Yb4YkR!^j z9By@SnzV>dQ+|PH42rZls%RVp6IUdn*%!iRfClu?wWci=AV6}**%sYc>!O+?AkQI0 zX(wU+^e8H^PkZ&p#ghhE3_B;H1$T)L5US_HFNOn^heLgxuP6W)r9c=J2jiBGm*-PR zcbG0SgSSq3Jrz|M?rVkZXD&ZgB~&H|$Bo1b!4iTKoFG{&un@Pn9juO|SdEe^6CnN& z!$VFIDrXK?S2i~eWJ7l4KU<xt&CSNdgqG|<Lt&rPArBrz5kfxV>0P@s>19RX!xWdy zc*HDX?4X^Ah^6x5Q0uI4ny;>miKvVy-A>@-_AMnH|KiS1w8ePT>qkt!>h&*i5x)dG z@bd&v=Z@s(p?N3=9{}aF>td2YnNM=3@7hR{^9}z}$+S9bYrj!-_;C@};xCq{)Q1EV zcLFC*n3x_VU}XR-@e@GlR9Vf!aDo7I9w1!Sj8tzhc0f$MVZo#D1L#D#>P4|P3?Aj1 zl!UTaA1idy%98_LNZGE#r`JNF<$}M(vS4qVtPPSIA`VDt+z&(^v@0HKY&{z+p#Nj- z=p2E+Kc}ZgY86drg$&#eYe;^SSJAuyhgr`7*_}!q5H%9$t*I-tk|7X*4I_>Aiy=(u z5&<1*_p$#cDDMCmm|nC3Vx~q)s6elYk(@^C&oPN#$yA8A6)~;&3<zY=)l~H}uM7kw z1UkY$RMW-Z7?S0$GR$;dtx_Cv_Nx<r4#zni5>Sw6<Taqz#B&q%qOHWEbn7OZ^!qc> zX$d~_ry!;^2V|=mn;}vDB6PhEc1+BlAg9U^SOt8XGFXx<z{L5@FdriB3nm@N!HhCM zo1UK?Wx@(7l6ri|fEtMHF%;k7K0E~Jv-gHjLM;PKp&V4?<Hvsz1d(&Ni=N}Zd7kC~ z_>eircu~LPBLYPU+7Rm2?P3>|vnpFj8+bjB_NcsIlbrtWAe6gXQja+0m{ekx#~XRo zLl`20ze59V055F4r4O}#P}pDYm)397>=_ynS+QXiUvoTaqw-LJ;QxENzhK~S9;Ydu zg3#m9=T~>tZMSk#6@f?PBv4QtOO7VV83Ol^aE{G$o5}1dzNZ#Zu-VV>6gj-PfWe=T z%HTs3OBdDXv$krFNA8ym`r;DN@hI|yS;AV%LUR*wmi!fI>3E)%QVZmsT1DXxYzg~5 z6vRG|#>Es+G0Dyx!>jpMZGCmj=oVu*O|yB1a{?Xsi|!FJ_H&31;jUXAd|*dxx8*Bk zz2N0F^J5W?N$2&OZD%_sxI++vQBDxYgZ4PP?St&wlJZ%e`yzPR^Jk8q(U2PDyf$}G zkm_(24qh<nJv_wH(!)0LK>X()b#uE_7GFBaqGCoQ$+7R|azvx#wNmDReK_m1g)52< zO;Q1#@GkIj4jCl^qeQoYHe!SzqlgP_*v|}3lWlw8{!#%+D_#I~)Zedo)PMRzD_nr~ zYa)=>4<(gkpqzg~qsme(FtH{tXfQr=SbR)TLlinY_X_@Dq1Z=->{&g#93mhejh3XG z#h)*}8j_4G5{jCF)$PZRQce;G9K{JkFGR5`N5*yr(1&U&Y*#UJEAH-7h}0{q5BVkK zClu`;p$B3F_bP5tz7C)akpk+xuhAC28nSF<09lHXXPJKnyjBHFR$)H<XRekt@cSe* zSmg%q7fPR*?^w>uJvyw(UMB9H8WZBIKx$Kx<djhTf|S@rf&nNn=~n_CUxm}06w&sg zhF|tHoM;}A99LL5l>#GvfY=}zDPIU~xqy<hy&%R+U)B+Y?>s(r1HM3BN52dug5*kQ zi=~T12mdIMP{gkRXSnf^Pb>s~p2yB#vU;mxg89WSF1y8YVsIj06Y@sEy|{C5b#;FV z)B)eYvqF1L0ZXszP|7k1Nn0>Hjj(@sdcRvidU8u#kfGbGo6h1yu$k{T$40W1Z>V|* zDz`!0o+^uQJ{VtulaTZ*m}`%>QC}NwwzV;BWH@?eFI8%U2b?S=>oHh6F|RLg^leBT zMNNBaDPv1sU)BfP(s+yFv{1H+bgg*<N@bOUss*j^_S2O2pMIB)fs`xyk;LY#{gYHV zBEU4+l)7#zX-A7?dIH?&sye6(y?(E*6yeU$KQlVbg+p3x#b}sG<Ak}aCcM~at%hX> z$ys?tnXf5NuC7tQO)wwW%`t4X$qkx0$q#gJ!{fm(?f$s*>tqC{Y|gA@>w%&WyG3M@ zW?w<3Ytp8ci5WEQT>5g#8hE9-k|5-VnDcDVQTVlAs<w+`aa#|$PQl=izTasm8(P4q z7%&*(u|*D42wG+AP2Ec!o?q6+8^un!+(7o(5fS_}vK@Ib3B+TyTVlV|*cwEvyfd_) zpm`V)gHSfuqTv082ze(59*td240~v{ydM)I1v0V)4?me^v=Q|NmJ0>3b1Y7lgns}Y zLaEZ?f*K%~-Z7vH%_EL`?{xbI=nzy*9LQl#b@QR6;jxC|IU`nei6!xKJD}5+^O<c# z7MX`@SXS9M=`hck2(fz0LBB3R1Rm^cc`SzKqXiG2>Eh*eOh+ed_*x0^S_Nj0G->KS z@1D8m8H3N|FGuS40w9dsX2w0^f$63aAHQjXiQ&<vhxWuqV`3zYT$&W2(=BkAIwk7O zBPM51M{Ry^kJs`W`#oIFg!0^wpADqwtm|1tXFo)r$Z$|O6oJFT!sjRfh#^kWBMP)g z#H>aI+~w~L@x90oOvKJQ^7B$H1kqg)DE5;l2!9EO^Y}7A%kAP<^Gn~@2uHiDn2TnI z|49=OyfmqTl|L3-c=<wg_TX6n8;?<`VS@;xwyDSxiH&F|A<Gn+Lq;%Na8P=S!?6;9 zBdiU=#@7ZL!)+Rhk6you+`>I$6LJy4X314J=oY|o6LAk$u943y05fs)xvf%ck@7pw zZzuYpC`W~wH!kh^WQ_rWwlxfnk$Ey*4(!OjB|t@_VYsiwFzqZTaYDxIy<HR?M4kF7 zrliyo)634k@RfGl?>c%=<A7G_EX6Ib9+#b^zvnBPe>+q?J3*F_9F;ckI|6;Fd&DHh ze7H>exAS5!)F}<N`2g6%T5*)xDU{Q>n_ckAE(dyVHW$A-(s)0BlLlDbI&_0b@!ClU zPh-uSt9fOr+NbnXjAtdLnOvlbI`XBy<C=Q@GCNMYTajFDS+!b|HWi<~!U_$=g<IK$ zEp|;J@dh|1{K=X71FbkgFL;Iv{St)ekdwWpFt=&-0(Dh6dFN(Sf|!%y+{gr_U{P$& zdTih|1z>X&`n{lYG}8=d2G-Fu0=q#VV5lFb<b!p216=Ak$IvJhWzovgKu!+Niobz$ z2dgr4Ilc@*fO{sgg8!!k$B^a+A5me!Sc1(4(0*oBP=0lwnxPXo$uhC!P?NAOeSVU_ z3k8RH-dK{c=-H5PB4Ssoih0JAw#`0}XookJI#iu(dI31hG?N1h1aM>1Zqr#f<w71S zdH~C`xg8?2x|xiz4}=~~hRxz*lOH#m^h8HWV^j$HbKg;F4hHdoI!3v#<{{ioKWf&S zm|P`VK`6h)B?4xvh%e7(Z`Yl`Nd%4hGYt98##s%LxOn7YfN~-syenkiM%B`MuwuLE zPqyy)`L0qlbafD!3%zPXX9<8~j@q_YvPRw21D8?2V@VDOKvmI6X^2o0tk;MngrBzX z$F&x+x~zQUyZM?VCurD$P7wgd^fq3*l6__po|=|0C;nTWTDw=fCl)~@@d(f|@T#lC z=EGG52%|wRbk$Mn!nZU(;K4yQVP{AB;9+*SHWN&822l1K{3z|+Ko3XhKH=9s2rQ>| zsHb#qroiC;GQNU4+<I<(px8q{et}x$&orl}_3CJrA?7TUVW=eSq<`8Jv)EP(qS(Ui zi(VW+f_`6yp`8um5D8{Z5D(|knR4u}L0N$!jRP3s)Q!6LTa<ZUJK2>Z-MTN~g;c%s z)f^GnZVt}pSk7_ewYx&b&XTW++2TGCH?r17%O-Y*1e#E)iDq^1btYI)pLJ+De+)g@ z6WjX?;oNKmJjlR>^jqXmA|+*01>y&Zc`vYozs~M=!LiJ?lL=_;#Uh;*QGDv*ztR$N zJ4`}WTv!6Xv_I|2oC9w(C&`zx_KzrWUot$cRqCrUhBC)g(Bx^Ca~gE-p4IceAHk{v zRxWS<0Ejq5(rD3`v|ibx4Zt9dz1PsvuK~PkeUGKQTXF(FIkN}PvlZTgkim7<yK0&b zU6wv!CG4#gEEwcSF}?xh2C!dS&BJfvx~}lk!XHB<?8Cw@4_^XBfml5A2Q+@wq0A|O zDxowX@FtGS0|7W`xz6Y2lEi86g*WU=0G+rlr1}vZG)E0keI2&1mRO1%M{}n(13E=C ztHmuaBpl`lwB(q%IFRs*sZEVxiO|nqpaG33Mg$W>sO>=?S{i?ih4oEeH<}BDBs(s& z3(X3mmr_vI=pnQjrTd7iAXp~G!Os86(F~R#J_XkM!cWt4+2qej4)|jP9uo-&>+4Ps z7Ya>Q+_8|hdTT4|aFB4Mt>7kv9>4X3HwdJDmk40TewID|6xy972(8*5IeZNW2&LrH zh5>5m@*Q($3tCXrniELo2>%~y&eqGPiY!aTbm!G(BI*{iQUygI9}#~t+2Zteq9t!( zPB3g%7ajSqL9t@W1oyx?npf;ZJip0amrJ|%4oD03jaldfpskI7t37-Ne5>hg(ZH`^ zW&LHEUY%H*^?>D%Grs6z81Dj(F+=+Sa7*C`Yv=<jFI|4-9qGDny7!_e78r)~ftj{d z^duxMkQDYh{dA886srbFHNO)m2NvM?&*^^N0JgU)tJj7KW3$N#WDJX0x+)*5h1C^f zdmA{5T>={WgA(Z`L1P`*GZIXKi%BhVPlv3BqgW`3qe3PavE4KZJ84Duu<THbSb%Go zvN3NKI}%t`oQ_~WF1t`?OP;Z(S2*sJKsKW2BDA;Crv;gdKyK1#mccRakO|XkNaM-? zL*V8axt&?*hLAYT=>F_9w7X5~L_W4gS8<v1qB?1o#xKgaXcs)`x<P4YL0nMRr^VlP zv#zve+CPoUFg8t-=9HtA$K=9L!_-KYM@e+dehoSdAa-<+_Wg(T9m@BKCSKG3)r(-5 z3h-TpV9&mHFNh09bt5j+iBy9CFn{h4Vl0k@wS)7=q27=WvtRT_NSoV)v8y=RR#k)+ z!S9IIE>nO-Tdnrrshj*il)tH78x+`0@@uSJ`@Fp{4$wJzQ1Y0CrJv4Nd29vK!ScIw z2b%HAy&>#<ZFWTs`+@U)PASqbcehlSGbOTL|G#g-<KW2wm@vQDUX`CeF#eY};q-L> zp?q~6*PH8oucdkd&txb|2`AoZ%cC7dQj9beHRXwi>~lDxfHb-C#jqnH&bQihk2?Px z`h<Jcb^H*AkHcH1I}JjrmPw=sq(X_@ruoGyu(ka4YH5<nLujktHZ8ASBXNk}Fnl%W zU=uSg`SqHoL-TPy^|am9O^3>Jr%|&{IHH@Bm)XEZA?xk?dA_@Az$MJ+D4(rvIHTRV z?Bjef4YPS}X&D8>tBLn67jJZEXQCm)e)3k?GPj;h2zsE?u{HUCs=V9*F5I|z>i{wk z05`_=5NG|GcV%EPg(|dG8`;J#B6`>siZc2ZHEyc|TY7{=-QOd*`6#bWn=0b!Xy$y@ zHsRjpWW#QDvb}mUNR}!?wI1V(k>B&aC~K?J5@((2A$r(-sFf_4Dr#I_!Ahi^de}}S z8DT*t(e`jv=4CI>W2E8h=5_wGG?<%k<e*$@?kt|%6oQW~QOFO;Ex0tb06RpXG0PW^ z+$F!P)V|xsS+Y>QXqiD&dk{b^6Eki!s;ftn`9UybEL@BvZ}gjXR=>+|zO4>k{vuB$ zxUaEYUbVQrqTIRI5pVf~U&hK3N5kR6o{izd%@1X1`=mb`Xpi`Hn<Mup>Se6UMDuVi zKm#21_9JJP{ISJD_xif8uqzwub)2ie32wY-M&c58ixWVZ<{HW7!{vyt;b5G}Bthc_ zdI;f5Xs-1a>j?GF*FJ8a?pt3%fQ~MgV+-91q3@Rup@q|V%bh0|Jrx%p?V!F&PfHID zHtp5~cWaz%=s*Tp<kfH><R{@aS>Wc+`zvXqV#Ly-rrD9xryWOmUEAEN*Dzx<)@ar% z=**&l)8fY!yep)#w3*+}QKxRvN@g9bmldCMs+lpfaIPn!J%r;ikcG>;0%qEAMsig& z%*tYhcYgjT7qytXjBc&~2)tV;U!<%rQ91}|u9>pqXfv*2z|U2~WzYo!Lwrh^bgd0I zE+u1_jDDY)hT%F^oM;@mo(YRL_Bbl+c_(#Op1lUw0mj}R#ZxA3Qc<*E7OZ7O>QYG^ zXd>majh%iJRSYsjZP36A2*=1E5^twCb%2(XYwVT7+pGG%vlSQ&ZlqL@zZ_f_FPw1h zELgpq9AZNDCnU&_d<Rd-lUuC!KF`w#BJ3o=;3Pt)c6S;-f*_iGbDwS?mch-*&Ts$f z<m^%}7bpYb^M0T*LW@04_X7beZ|?!U{+Q6!CVI?4S0j@5EwB=H@z&{aAhum$8SMvD zeDp96FUl_VAvbm|G4LMl2Y!c;ZzIT5>$VPXfUobM+IuXq5KBF}c1Dh{3r|6rzdbvU zu^uH+W+LcY>G5{o5v!f;Z6f5nx=yB{?<s#(<WsTaypBjbl4d0C`)dwo=9>{w0Zq?h z+Y9_^(Ome;T*EMlknngTTUiDy1bTbU7iT-60mqlRh9iO&IejJBWYA|#pezdRfOP0f zspNtBvOnLE`}%|QuSZfQ{J=ha)ARS4sU|n6Q^2{O?1+pjd~O0T`oJm^KT@)&Mtp3= zAG3~iK+%pScylx}@_u8%T6Ix@Rbh4Dl{`VK47}aVFjqlk)yzdkR@I8_5Lh>&c`jjz zHk!t&E~pic-3_Vk?bGg4P7MKgJDa|&W_3hvQ-S7QN>50h&>BHBP8@o2qW5o^tm*o) zs`K5{>!bv1t?rUxW{u={Dqc?>vCv_i9~0c}FUGv|-~O(rSN?O+F?5^y^|}=KvfO;> ze?FbF`5-tSLdfI%O2c)wZU#fD7BGAa!}V*`3j~HW{pEVB-k}@WFrm#jGZJ_i(vQm3 z&gXOWuIF7);cM57FH?bOgrvxYPKUr(tQ8LmKcQSH6D>?H;ux)(_6xW<Z(lb47*CJb zuTIvGf7Ffk96sS#>Ty=^v-;foES>RPhQuTKjtfX>CR_hE;O`4yhSbK)a9gzet;RSn z(F&U$_VnI$dY`;Kd9^u%@ZmBNbdcnB?==rvNjV%EUV#Pw<+9wIO=P{eFDm%Qkl+q2 ztj<;-S{7zf#);GZb1PW3yJJ|{%6N%Hzb(4k=cWFq03?Pvkb1{kiagehbDmBNx6d?S z%@NVq=ZUPSr+O5o3Hv!K@5?E`5U*A4v_zzChhI)qUp6P6x`8)l(o?iw$8foRm0Wa< zZG+;Ik*2db@FCh~%KZ(tmkVmNrP^K<M$L}{H{NXb2JhG#;DOHm!2`5Ip@wem6WS#^ z4u~5*Cz-4#q0OdS0fq5Lp>u-owEjQF;jeh{8Fip&tMGM)T`!+8%o+p1N3A)!|I7~V zC<rwyZ;dRKB{V<RCnyD18X}*eZ<ti<XCmAZ9x(*?$snosNq*#TEWRmvsk;{dht_a& z?bD2ZVdaWtR5W9PR=Y@Z&vJ~s@@rd*fjSGS#&X)h!`1A5(CQ<~Z1+LekP_}=ZT40J zF!0t8^egUr2ntESgBj$aVJDZH7GEK2*%l4Q(r`(HRE?m5L6?h&73~<z;WwnsE#mlh z1X@q*Yk>xp=2NYey$f}=J!~ohrApG)y{^|fbD{KNRgPj+a((I;gH+uC897YmS*7XU z_fs_c+I1xDLNMwD8jY?T$#);G>8033`uBVu_zTk`Y1^PVYb#({?C{PZYpv3yLDt6? zH;TBmu%4i6)j(cvGu-Y}c%ugNjH83-u7e<>F7h!$fZ^`>JUyS|bE1SnVn?S8dap5L z&!4oT2Lg}758MQHCV->T-&h{KAbo|iLq&AxxhQbOOB3~Ln+R7f?@AaHn)E7<g~$l- z8yR~c$HEZ%C&Lz<4GUX9CC*NQMy(}fw+Ygxm{dK+6{qJoBr}yxdJZvPgI>)yM#WVB zDp6^*%3B$BD9h@_Gml2=>pPq_qlcn7$ZbZu=?b3mN1Ix8_Wki7HN!;{++2sGpzKxY zA#t-C)4!6)O?mMuae_&Suc{o@SfP}77J_eiGZg05l2mcfePa@pP_Y-;ha092nT>9~ zyxhN{23a2AY9JmVKS34Jzec)pk7wxd;A&8|8NdnGfOa6@`mknv>>9Z?Y%sd`e6EzB z0@;%WXN}sluoS=x(|~lLZngYT&|Qa=ei!MHAE0DtNdN5iLE<w<wxBS|g%gB1QaJS7 zjtO!iN({!?V>p`0?i`{8*hslxaFcLN^&(Q|VEas9CRGorWRc=ksJ7ZNDcRF-fH@@5 zXRHI0ldkkvv^)NmNQ*`V1?ECA<tf_WaMxiN(SEj8E}vq)yj818OKZ%H2IducjCF^V z-`H*Y$0bA)8v4x8Zc8Du1v^7_B<0Zf&@T0FkG%1P3Fz-}-SMYdUeVp#f1Z3Kh8_QE z`rD#Ku0m5|#Dxm_0da_#K0TCdaJXmjPq$ciz6l2^=>Ji>_}y-|?aTc!G3$BTWJ`GA z@!kE;FyxBw$ts6DXlwrS_djZOCjx^tqN0xXEb1KWndjTs%v6cCXBl!L1AT%uVx*4u ztjH6yzxw@BZndx;D3ffBI8aS1OE0@^zo9HgBu83)L*efb0Iv8}q4BMP`X3c!+oNtY zHOQ2x%imK5x(MGvbMLpYov4-_jyloQpq_WN=KQN~Inr5Fd*&Y>jkA8spMU$~&$qff z-Fz>?`L<S_Zx{ZL?&yEI<GlvH)xoSGThM<4#{3=@{S7!z`+L}aTdTaER+gr*AzO$* z_&&K;HlXV2ibC;GPCu=_aI~ie6D}2w<GWrYin+aR_%4e%zsxW{Fz}UCrX0(fGs>=2 zzSW|KbUfq%Rd`YED{p4Qi){|)Qx)wOUvx9@WvxG!#ARQHPQ#|p`ug^{v&i)}j1MjJ zOvZ;yIj`ILHI&HawB0o_bsn?uzO0#m_(>aa#g)p#+xYrYf6(4IEGrhC<K0r-HBQos zbDl;DFaxfYdz1~;@q%OOJ!odmitE*HTWRVyeQD7nXmYJ6Nr+9r%>Cl)j%n~3{8zpS ze5BNOY+%M?uuf1Ix#$_j5xEK{$al?}OAn)GT;e<Yy??f4#`j*nwn3kF;+b9JI}dKU z6}`T-qa3xc5t`lZ+&Q5*F3(4A*WF)NfAnwC7D_bu?3uNmIv1>QlQ9nwYDR#J7jFt3 zPB$E8XL}lU!E2NaS6$u_Q|eyHKUGw=9SSfMZ)<l`mDCF(TYdqX<w>4QKhW;05`)`7 z4<&1^@EoWzAtK&RAVZgey(oW#?x?aeg7)XZN%>K?D@EapJJbC@5)bDHB{s~%0{^3L zl-;o}D*cQ~W-R!oKYI<|pmV+AWTQeOWPL!|%zT`*(H}d&e=-MZrvDL5vmt&El|<Be za2@Z=_(u>@JVK~KZO4N1K+vM_`^KF@>2*fnqmNN-D=nha&3^YO;8q}qAir^UUgcyX zR;#3Gz`6dgb5DQ?!hDoaSjll)3oO$GTjB`Q00e?BhC7LnITu@DA<@TRR4n$-0Frk5 zfM19U_7F_WEE+hz9$FnE>1J%%8@~;|*zj@dG3<kWd}X(`C`HL3wIO`s3Pq6pR&<0< z<4#ZHdsCdrQX9SHoPKRaP`fO0=eeLu;bhImB<l%X-ut_wjstheCLgv}LqS;fweoI~ zlYsV!*g(JQ>bM*3D?T>x3@Iz>tT;A^y3K(tr9@)942?m*^qsutO<8(IRB9;l4%!^J z^@o7y(r(S28--TdkXkOL(EKSEn91U_i&17yf)1gi55eQ~5LSSqX$%RGGgOCBpt6wo zE^T0<r4P|~z5<uw1zF+B(q&&nLK|<q7{s85#r%hki!q-)!n0B3fl1|fz*Y3{k)1l( z1~~`*G+Mlm`Ii_RR7dbS5Y3sOcbXe677^@?4)G#JNCjF`$WmM*A5<AY$8>J?{k@Iv z$_wI#+royft(H8+g-WSPnuKWxk?N?8D)@tbC%GL_x^{9%f6Tgh(|7K=a4*4{W~2gu zhKm)*g2ki5vGyv8KN%#!#pI{T<t$L(6>oGVo6W<MX<B90N5Og<q|np60z#so7?MBt zR)m4*=H&LURN26kh|xPVp*D~K2xakL`qC=mz!t7VcaAc5ZBbC#%nc7DGx@m$-=OSk zfiJAt!nZffaxPSqv}t-dy>bI#4wOMopu+3#7_=iQ9VMiCVLG86%i5b!<esNPKz&D7 z(Xatb_ocW~>VNAqRsi>5<cVGW0{Mh~(xNt^VD)F{wo`+X0Du**5GD1u%YaVkr2`$I z_e?%1P6QGG7c>GQQMjGwFa9aa2M$s&U70sx)d9`1hw#$pj^m<JEpsOa2*bj5rVy@^ z2kPhrRs>4Q!ZCP`#w2B`Qzpwz3k8dvcW<#4HMiG!^Zk4AL$MOm85x09x|%W@@VV<| z0iR*WXs%Om3pm3Yh+_X|bJ5|nN!*V(6J9}ehY{+jN#KK^RvY#@)0X8ct)J=eAKr6V zF&u$9_XQ1K74=W&IPdbtt5t6fKpZ{pUM9jVP-imtcd}}mveff9J-m>=gfUy7o4&6r zXT~Y$sP$*?qdbzb45Sc4Q-F-%?j6G#t)!vp#kiRtPqW3t%dU%g2owwzpQ@9TaEC{W zxfLmpmCYRE^+$wYp_MW*UuV;1!f@$sI$&Y1G*SQ{DGz&j5>`~aJc?`=jj|s<gDV~a z_1;)bjd?YL;8$F|J*u0|`^2*O&e;`B>qgl!x_`Q0K5U2<eLOkKMg#9Cp~gO{mIRS7 zPJm%-5H!DEEyCW85fPD{XvL+zI4VWL3_ARYsDyJi(b}d1d`%OFG6!Z!oO_d?YS3~r zg1I`W8^;~>y!kVLj*M7Xk|}zc?T^_q%7IFMQT($$)QXa1`uYoK(7q{BMR*MND5(PP z$c%LZe$w7@%n^QID+*3Diz14x#s+?C;e8jH3J+!fq^yI`ZN1*w4t~TAWRB+USy~wp zWCP`pVAT925Nm}G%7N*&gD@_!k^+lcshngHT5t((bwpH@X3vR38Cf)oCW@`w27c$^ z{U?kR9tc~8kq^=iSqTZJJ3z}8IjX@0tJx?E#ciUPkE?!|HTe%j{mD_>VJP{ix$TN^ z%Hijp(ez08kg2EwG}gC0SJV(R-4-zYrg!`*k_fujNCS7_l@z|dA+R@X(p@qY)R2{L zU{Q<rq4dA}S7gL}zb@73hA*nf<;~t_dxwzjEhM<Cn1kqtmZ<}j2z}!K5X1N*K4Eli z@S2E`?C6Obbh!{=l<JA1DyYwde*uKQ5SsQu81w>kjJW1F22%O(?Es$N0==bt_V?~t zw)89Gi2JIesXjnwZ0uM_itPXfvS~c9D0^Wh`7fgQqz+2!s*aB1H^o-tq16SAc^|bt zgrTNLz18}5Ca?o_gWcot2KSCE*AoddBMJYuko!Ppw58NKjO!l}`F;pZ7e2FdfVnF{ zW?=9BI&u$vrx^D7MyLXl?qLPYZv^i6N1YGl3u#~*!Wjpf_D5Ce&Fzy&6&Au>Jslw} zFDLQL4qb7!9GL+z(XO#xo+6LoVO+?F3S5-hjLaY}XuS}DiolsR$euc!n%AX@X%$w& z^$J)(tB1?$2fH0HQXK-qmBU#Sr`RNJEb(O9M2AfK1h~2@yY(72&=IU^{~%`?cj$YW z*q{Oe!?ncO<hLMp{V(LfM}c3SF!4jYLv{@U&kpQOn30G@5OLGxIdyz`pLWEPoq?;4 zVU+n{!dRE5lN|{h_b>?ep;Y=u+N;h-+Tx>~l;(`rcyzw42qs&77acft|D_$bJ}BMQ zEk0S$C_L+**0o7auu0sS?^fgEbXQ{h`<kNZ&cMNUe91I-(2i@jL5csDw&ZNnt>mEp zJ|*@=_dY!3-*Ho&fxYj$5^3(LE;;V1hz!>j{~DC$`s>X`l#2OVuH?VuKpT$XR0$?K zGUD7-6-~GJ4L;-knzrc}M)ea%QhKzr^1qwGKA0@`7fogPw{7&kqu;hIYMX8?exu1? zxrYI`52e&U!k+9t{|8OUe_5gGn*AR%|C&noRwG*Vcig{@+x&Li|D2H%r>^l^D&?<~ zV<?psF#QqsMUJ2m_>G!|z>&6OraP$r%Rn;SJ&ef=evObD74^4y#s4)iXvZ;}I>A3^ zivP>z&^68fYl!8)hUBlf!Ne0I{Edjlzc8)8Bl160{wE@q|2n4WJ0kfj-UYQQ|4m=t z)i?J4uQKj$e5HT!ZQw&{@<;s*u*=}@??`$4J1Lzfj6HF`;VF2!E4T9zHqjBRdjBNn z3U}xz!cN`8w>xtFo3nESwZMh{dNSu<Pi}vEQg*cS{9lOwcL{p@vmX9gg5JyD>i?qo zFUNp3TzmWt@A<!8`STs#KmTLK@5MGDHrk2#kCf|ZzY3h;R^Ju7P|A;3V<Mxi=*Dzc zxHMiZaK>xBN31=Gpb>^|H;DW@@!$We|2SIYUz-iT9ZiUTZ}Weaz9r^8kAUxs-S(v& z8}sqWl;xdj`8p59@_6ywCKh(`*uT49aX<-l&1UScpx`Q>h<&YXhnn%?iNboNe;D3# z=4zVq`lVTM`h&8;DNoBiS>N~yC}fyyT;=T}D0Nmv-!4{o@!^!pN_prpISe&-F7lxF zicdj0CdV}hpb!sAjal)(v~j3y+HCefe)`t&)pNeNV=3lChwJcrI?MN+HsH12sHn2v zq%el>Hz^GEU!<@dQFf;5Xa`?I>?q*?9OA>q@9^`flxFeBzOs|d?pUr+rr>Q>i3pt@ zOPIAciGAcvm%m$AYvPLX#wCQj@XVgBZXG<{fTT&KO6t@t?Bot^0wbrTwi7LunWv{8 z9f(hl6eq2!{t4Ssr8d{y{s5fVRj%!}MX+;Xm|aVu0xTGBUV*Ntf+Jb6Qr93anUL!) z*e6fuO(iMZw{m)BC;>a=*Ws!_U6lz`=JovEqe!oW<9qSEdbF^w2C)WJjCQHh-)9B_ z$$P=Zs^@5%YBKZp8|$DLE?HxS8TwM$9!G-$G26`mU^XKVn;ef&Sb#+u19HfPOMa04 zXq1=J<jh|oK)j6e--WiY?hB(yj06}KEt%Yw^*OpU^Au;arwM~urR;ul;eu#~{Q68e zPx25o_8?;;f!SDBc!DS(aW;wqg7J3Uaz#bC!T<|hEM`jZC3wXOh*E>gUfdug0~;?L zRCf+14dWTKK^`n4q<4TB5Fg+{ocTm*V?6M5J&YEA(y~gQ4Ou9@$cfY+_x!X%-sfZ) zoCl5yM-6E<g^T{7S6?CLiKUK!a008Mk4d2=mk3q>34|(4tZC3V6PHuTBb7>$&Ja~B z=`c`sjzA6s`#5aJa|}H;7`zWhmq|GM@dDUV;J-|Y?i!_HpG|5O8b}dQbs91!hf!a# z<d0F5qyxRy3j*=$4XkW-A`ZW7XNSw71z))0FLOnTFBlMrf+eQHvQrr32?PEXCmh4= z<D@iv1z*nhb}^v0S^A(ypb4Z(FC4c1ytLAoxyAN7Oh)}`o-6<f4v1?hfFAQwosu<N zCUdThR)q3+C3S%lrZrP4QHAOTIO1$D-p9J_<c(%dJTw+<oGU6bl(I=52Lyj3IR9FR z2%}?MSiI^6RAg7LOHWo;7dJ%v3*{UUG{4$h!P_%~NYH2)Q!NdB7q?sp+{bGy2}cYr zGLc-r3@bUXA$1Z+$Dix8@(3wHN%a)gejC&Q!o&c5l=Q@rJ6mzE^r4L43|G?fJ)G_C zdz*Q5_AEV0J~u#x_|e^`|9BXLKqC6<$$?d14iN&&Lsy0@({#;j;5)5A_Yt`9#L82y z+|CgGejz(oK2XpVfr-mLDC!XaT06>(7+*Qn*8fuKw$r-#K=eBu1-#*p|NX3u{y%4J z=L+j$D4#vbZ!kC|EFn?w?YaqZ<h@4<fZrLXq24nTqJAhFjS)_y>$g1qd}Myy<`UJh zl69>to_Cn2Gt>hBOeUzSy5iY9zRS)`V&(nhiNMGWa6%xtbx~AYFd+;J9WzGLwPh_` zT`XC*dVhYr_Vsk!ldZ?wl-#2VYY+zv8A`l7l1_=%wYHWHVeK2uB`dT`BS}1<Pz)I} zqik!32<=-cg(KPitt`Eb@uI(BWT;!rSeR%JH_)jn&T-ceo=~*>f^=!bhW%adF5JSl znE$|(@9KaAOZLR7mc-B(&5CuzoDTfAEdTvw=4Ui749UxObm61RyafwLi(%_Io^;8+ z$v#_DppOe?A9CU93T$E7I}@1COoXiM_64x~!if#*O#gG|s$0*d67qy1wdA~(w4(FC z(jSxkO_%$FqBi0U@4Dwr-q6%l>CH>Cr2)Lo?y);e?&SsQsRnIHX0Cz0N?cMwp_(i8 zcEe<$fsmF=r?J#usjK!`cJuShn-r<}`)J^c{+zeYNObKtUT^Yj)3DVZ?7Bd@SHOuk zx_#mf<mu+L=<xV?lAU4o%l5y2qyy%A*=Ki^1YtX`XWu(F4Sn&fp2Bi;T}_LddKWf5 zT0r}dTVlF(8S-}XSj>~x{+{~4$t3CSdvfCGtGI-n(?NpaJ;Ed!arIa_h2c03%#Ce7 z?=K46ZlJ~Ho-1SuCy}6ixkQDo9R_JATvbAzLhvQ?-!nTbV8Dq?P<hGUM_;_`+kM?3 z!K>FH)`epzw22s)p6lE7o0$U`KOJ8j6A}k%g7-xeep#uI>&voYLlrx9@6~ol52F30 zhERijS$mBGJuT~buEd~u@H8YMX=!bNNxM2Fw*JbG1jX3Kg#$9@78f;upB#SO-9j}_ zPfz)naBHn$W_ep)$FpJPN6d&px}nALnBbIEv`|GJ&Cig{L^DqW#F^>y8JNcOT)k5s z`>~G44K9KkkW?cBTbIf7*ykccsN2en?z52|rH}a_!@m-?7>M8J>?Pjw&SMAQ2=)~c zh=JuI!*T5zv%Ea2?J1sAWLf?F6>qmOqt<rSY`3rm>m+%)^r1RxZ?CsTz7gU{&5<=x zr>4uNc6O;?<OMtg>2!ld!GOA$<a&X&q!!H0-^cqyD_GEi&xt3UDCK9PVQH>$NtC_v z1qXZ)b~Kf^-Li!7u;m=I`pFKw&JTasl^c>Y<D|>7tAy(Y=q3VpgiVJz3t0v5;&dV# z=F$%w!*~5*HZD!bpS3wZZ`fXUb*hJL^h(!R1hIJr>)~zi^cu=(id}H6CEW*7V;vUi zokCJbp9sT}iYKbcC&dpRyx29GD^(-}E>?fh_+37}p<OL2HQo8u|F<S*9Sz<Q10$*Y zmz>0TkL#22Jl)#Xk<L0j%hYsD(<w$y`{`Or-TF_UCFwWb1x+?L(9uAvDTMPtNoOoA z&|Bio+$1>f)$$exdZYt-Q4BSFRPhkiVm=37GkZ`&o<{4XwY&RA>IUEn0&OWE;m$Wm zo;%B%2dtywigBvh_Eso+Zn6{WQ%u@FxIuzFJA~L3%E|HLz~~ctl(_GKe-g^*>wZeq zyavFp-tPm}d<LM4Cd@;AXPF^7B7*rXg2co?hxd~mx@5#zD<&O!9G!c`2H^G`gI?Hm z$W>XrzZhtr+8%qiV$iWjLdM&1@-^E$1q-NcV~2J&esPWKRK2g)KF*}2Si-B1#OL$6 zcHRz)ZW&pMMeS$^eAngcJ6=LcP;(ZeE^r%9meOpw#kobRvVGLqqV<+OneSw=1_+}+ zVSeuhym!I=zKM{o-0N!km<**mH-yQaL@m2jsEA=(+sMK7FTY2Rn;=OKF4IW#eSC>! z!rG48?m-~xA`F@R12;DFP6k)_MU2FAKd*OBk+ucScw;d0J}UT=pI7GH&EpWZ!CXRH zy5=Fk6uhQcOZqrdnn%^V`tbwSob3VMLN^>8+d!ZkSa_bwpajVAK{}nBZEWI{?p($k zN|i(DX#i9WL`vs(D-?!dz1{O9TpSTUZ7<XZ_=ex}Cj=WHyXskyJ+uZ6Z%$1_W8m2L z*ZjHhx0vV={Eeg5l1_dTM(Vt&Hu=1GK+S~|Nnar41SGCN=CZ!j?>e8fnlf4^NVH@e z)~ts<;HSb+(M0guqFi-<N29o3Cg9y~Ab+OKu=qDC&rqpjze_2jx|He*L8Lv!pmgMZ zr3TN8=-L-~h%>Z56<)u=8xhDc-7F`2m}gjorXY!>bd%V7A`HUW3z{F|_GES4i0&`P zElUi{_!`Ms^dFCt6u2^oRzB5q%-iI;EHAfb_kCv|$i-+Jo@G1d=Wk?XJ9`*HINy?{ zqhjO>GrY7@NKp>rt44iJEwv{aBMTQN)1S4m!yb0G#%(6{YQfzknYX$Ixd2!T=NiO* zUe@*<tk-(Rb179%8GO^TJ4Je?#IIEuL{{jI!$7CyJe*|bAYDk&>+88P9ZO5uJn{Hb z%<hoOQp=;-Zev7#IU%{Iay6>Jp(CNwmHAye{Ewm8VxCoc_5PgtDYc;(@X)||Q_3qW z{(Uql*Tnt06Nfr;jLgV&KzmMxEC~)^(fvBVPz)fNh*MA&G*iH&4wEq|^{TL@3w-@o zgJXMrBu;l&k!eXXuno}`#X-WDssLpq$zy$7Y*{)mPl`i`2a+9WBm}re2G-R!5Pyl< z_ALwivIwuiAypJDnk({viwUO4vy^cBmK{_TufRaSVOouaS&3<7bAF!+KKc;pPSHsi zf+k)tbv)uent>-&PDS1jy*Ye}XjHNd@QAaN@`QOkmqR-w^R(DZzS?|$#|SpWDA$(u z{v@mBA*}`hSx<y4>;j(u_?7G$1!G6(h4`wC9UM_1o}_K&!mB>q8OecE8jimHB}tKO zj1H;<-)50-;yX!45AK`cs`DDcoXp4hyJUeH0RdtqRPt!zfg+t4S~yy#n>n00QAv^} z(fR@w;Hxs!JPS3aEfG1>oHNu6qxFEYbu2~#0<oTXy=r&k_koasq@re@DnZfWbo7)1 z537@pI21mkU;o&>cZGmx!5P3{Z}{CIvNS8yCKK1Xy>n_a`J#g1^p%sjg;5D>X|RHB zM=Ns&+a?&QvNU_Djqdo7!z{2ylM*mMbPz3)sC`2jNVg63AiyKy;V7mM^K%$JitP{n zsz%fN2ulE(UcG5e)%mV^M8%-vfPosCALt;9^|y(xmD7F|c4byFC20|01r&~LY==W< zA>@sxVRRImT%eBn^T{^6;f(TCWKo_V@+u#`Aak9nn4wT)5wB~rA*+?Y9q^9*G1E(- z3R)+$l&c|{*+71(L_f5(oq1Ws=am6`I8+o0xaGr_sieE_FZI#i!~*RAr)gps7pK_M zjPDiBR*>d%R)FL~24Qmm!8n$yBN*qOSF@b#(&0BPv~>wRr@kq#wDsi$XJgW=JC{Ym z7RE(7%qEmU-a1mBB#>|bwco%8)&zEfK(%8yX=`Y8Yr(g-2B9|{N5HKxy$GUINEirE zI5XmzX(SzUr4&&fkd24K1!c+hikG9h{EXLjTMCTl7gUs$fg&SRNrfPQBsHu{+%*Cl zr68z(z{<f_w<@FIOS|iSe!yC;&+$sg^9(sq-q)CW5D+5`@@%hk53%fR(98i5Z*=cF zPKcudf?&S`vz*bELOGR?CTtL{A3~yEX!yx&mv?RkH(|+AbcqL&6YGHhR^oW*0;eep zQ>QTh<_DK~|5AycvST8BrZ$Nc8E!bq5>;D20#TVqk_R556xrd$#3}frd-4G#&H%ty zUL~oXBy<6}VTqs!1)X4ho;yQ06>c8`fF4;)ct$m$l<ODSl=m<Kmq=29!Li(hE?bvp ztV6hLm{h}q{ja?oU>wlEii?&nJM;tTu#<4uPGs2B(`LB{jf3zxfZH#m_v0;68H>~a zLoz(;lv2czl|OiKfiTZ5$9xU|TmdD9`7uWocDZ&aZozM!A~Ed?WLv|P0qZlCu56xD z;=050a1v#@HI{b(hBlFRjcH_pM()wuV5FlF8OCRheGXMhkm__w<ReSm>Fka;PbzW5 zvWYVkip4~hi%LclYX?w$4{1HQnO*c(#dOvi*7|FFdw8@`fH%`ikzWrO4++eKeYS$k zg!z>F>(%IO47Pgsw6aH1iK>iAyX{j6rd&%dz1w~dsBhFs^=%X8?j_(kRp%Zt*1fnD zy1~9XCmddt7&TXHaKkf>4|RyCr0z)Nu*@@0CNGiiUQo@c0cim+Zlu91nlb~)T;bQ% zUX-B3V<FXInY|4O;*bZ4_Ctus#<*<(>;kXh#J?oS1B`x|py43-cGiYzt0oZ?S;m;6 zBd_HF56UqkCPYwJa;dk{`B4wd>lbDBe2Fk)m(g@6?;BY(+i()$UezzpCr!_!PMlMo zWf>reB+?};nA_+4So?{0LqLwO2S!_*xDv;Mty1p_hRml7@1;2}eG(KfyW&iR0nAH8 z8Jvn*ZV}8f;FrVPYc<nMjodd4aD+TCJFj1C22jZ|>7-R=(uzZ5%M{77$4x{DJR!>@ zJ1gNv8)Le<L&u&IDj5#>3!(6FUZ1#}Fa<hHnUDmG$NOziuwDVVveYCvwW5$cCWDeR zp#j*<ywGE7f47)`ZsK9Z-A;D9o`0S1e#JIxp>s45%wR#jkMe9=u?K_8CrCBHg2sip zwXlc4JflW2j8z#SIxX2k(7&(-IQ^;k7m}(CE_|u{0D#?8<6sH!5AkVJX)16>6-+Q| zanJ%Y;Pf7&Ud(2!o4oORt+?e%1kt!|C=N>SQ!L|ji}?#CMs`TErZ=`GjAo61l$2$V zRuw@_^YSHu`kaP#rtPBAE{pF9UohicOqHrd(McRo%K=OL^`cz})AM8_k6Mr%{|{gH z*qm9|wTn8o?WEJOZQHhO+g8W6-LccL?cA|#+gW+mT2;IDu4ljB<{y}K%yC`w9H)7~ zW7jAxO|R9pV}q9{HRXwH?g?eT7K*vTy99CEVZE2=JW^I*e572F#3;CO3tUlwa*NFR zS18;+(?+3Riv-~r-$9Ed?(o;)IeE!RhL06bS#IslcNbPogH4<>MnroUvsNBadqpNs zk!nh#o?3E4QvIMJC0{hx>D3arV4V~~byRY{O$;YK7;GW6v8wt+c%}4eD|nDR3*AYB z*{KK1a!;|?I$brDY4MeALrZ*!W}^F9R_vFUlrQ;cm57LxsfcS9E>_9$1=7!Pgig4X z6=z6Up!ag6ZD6me$cuf(1G|?p3Ha<OC?_j-%oN3G5c3B;k~>pyo0BCfaBPNs99~nc zpO|cU*z~nV+tu6o>lS*g`%dRKB+;O1SX9Q{Xtpgi2QkMj+@JGzTIX67o@+L&2z&d8 z5o;Ow@H*E|o0rYE#@}CHl8gX(11NBzO(*&Z={W$OGOEYVQBQ~8SR-vqNXL_&azYg~ zD6EGG$uALEHOxmC>*~9Ghn^7F$6h_DA)GVKeUA0wf2@@qIE2Tb)9=%_iD|!?ib7*l zX=ynIQUO7-7@QJY5`%I6de=o22bV|9uzMnidhwOFMsZjN#QF$YC&-`>-Z$HV4j<I? z5#O~#>;JtYb<n0{GaMBN=*sthIDTOIzsC<w#3PAcJ;?6}c$~3wFB0X~nCo}YB(tQk zAx*u}tUY+Vvnak0?VPgAEpEny2KvU|uQT0)ohxRluCC@nbD-@p=5Pqcpo^NTo}X0I zm5=+|{#nu1g#9iUq_>8;_7;y?Scr*+XxsQp$E%m!>b0MrW&Yo-g!I9h7^NeU*`bbX zm3^kS7j=B&4X4kLkd1v5*<g*sBsj%mG;LEj!6r`e8(U(*H#@>$gNSgl><DCnXv;&* z+hx+~<NyYU_ST;rkqw%^C9j=Os!bU3dYc49oDt?7@JxEIY|v!Qv)7&*Eg7H?&jBpi zp{L<>Ux2tSMFx2DSAXl{>VbCM+Q;iMhBFq~BY@W>apZuW8-JMnq}L@0PTYk2=S^$X z%L5>8WY<II9B-+%=%rN8+gW27r{-8I)2<`6ETjDp^maM?abn^P82i)hbB<qpz$O{- znRlYjb^F$4>Oodr@SP_5N^Es)fIbm;Z}Sd*vu;9?<a2GzFsJc0XQ}QsCt~7o>gP`R zMXR$LdpkzBkA}>fDYmqY2eW7TxL@CWK=UDQ*OW;`q223QzEqnpG@PDGj%?E51#vsJ zk#MgV!VM<u<5Kqt)Z(+wZ_BdT-mRJUb$i0BcKgaLGwpIy-J5&+x>wuy7`@aJdC>is zR_=~%lm8Tk=@u%^(my~``+9g}-{OX^$C+(?(X|6$zy09PcYT3U__^J{$~vgSAUoVB z#{T+<i7!uO0X27N4Zlf7AAT=cYuBm!k3hSi`(|$$_DK7)ar2A9#IC^ubC79Dm1KJD zxXj#F(ioBBn3*1>$gMKrJrFUd=wnT^2sHC2WP2B&Xq6R8Shwn=%(=?Tb-k_Q>fUyE zRsY;t`}EM|`?=_D#NhO;$AJKuv8FfEf7_HNQv}Q&!#;PBc78RrK5W49I>Fvjtxcyk z;mFh_59?0Px9A30YeiGBL}$8CjZc2cBI)U3vtT6~q4m+PNsAag072%1yb*qC%HfGw znvF;|EvLjC2*g7OX=1_Tv7Ifu8}=e$<o3-7iF-VECmS#3<HR>=2<GJgJ!pl&t1oO5 zHVS7p!IuNhY{qvJ8eL-_bgT2m7F8;)u8tX0Qu##(^+|){6t#Z-*ui_0*VTCBnuT55 z^!sKpN4+)zS+{J=geqNaV+)u;*<nz5_?9DmDp@8W;vc0ZL5@nRRN*3-%JDTv4>dHm z&WBP9u0oiLFY;FCZ9G6dvcI>q9{5y`=_-ek@@skSk6DJ<#5TWO+fnx-U&AvmVEKu6 zge`8Zr4~E&ew#-zr#oyX>cb}Zb$4kCs=UZVNdK=<x?K9Wpxw-1UDn26#^ON>JdNxC zOO%j}WSSw*vES{o{>ruGgDpeG*Uu&A#r`$34}TC2A+!OUli0Is*{YSUtD8I5<=2e* zrvJjf-Q}8&c-i*Pd$+iwvVBDk4?+ZI7zw3n3ye<OO25;vz8flnZF+rY0_GP8b=q$i zv@KCbgwib_vVL|)rf&J8)msW2X}*=5a${-!f9GJRjKHOiEHe!xe846n$HV-Oi7fBp z6f36N>J*SDdNf7~HF;l#_`V}T2@=I6?o!d};4aA=l;X586|jq|rRXIvBw*V|B)3zT z65$e^oHL04q}j(ps-;`R63t_U0+w^~Q~*^(Lk|=AcGJ~8ZWS8i5Qm0~J!zn-GeDY> zr#+Cc%>(`_wZnx5G<kypAzFPK+yD2kyXsDqe4c-O{V6f67g~mxuS5QgJ?8@R#@LMs z<Wh-?vB57YWYURI2@M^)EE%FSufv8~xZ%xk^R_S0_G7=7eg8hln+(N$-`9Gwz=My5 z6~nh-!{Xf*N%Syr>w;bPGh)(a!h_R6NCc%w>L~pZEFj((4Fz!|#5s<T`DH578acZH z(*T5#U|O!rdpw^Ayt(1X5N3i41w}^87n3wWWNJF!^Y!A5BP)LKsShX3n;`V!6LDD0 z7pb!Sj)q`NJ?n{KvWv&^0ru8=Z>yR}uN)Dj19AJohGlY}T+43aK{OGSVqgLYa7F2q zE)oPH4HKs<nku9~ER2Zengd9P2CWcMDFu>*-wQ5f+&`NBsmT>bWPlgxDrSSTR%UZ- zQiak$xs5s}`h;7P;Q#W2%?vDL8X=g+Wtab@UmMlbzX~+Z7Hk#JQ5=#xB0sCsyz6sL zZTdIrMar2CR)0gY8y=|s8)>M@4Nv6s_h;xf$h4Hc8`mi?4B{|-o~#+1jJ;|gY>9#z zrs~!)g4*Sn3*2Fzs7{mT)yufgt_6ZC>q>#|la1rUt$(8h^%pqNqHN+=V^lY0biqla z8mPZl<#otvt>jzoa%3L&ol7FLwiJ*@PH5kUqt)o^ut@iXijUtW9)h7b^0gfgqL%HQ zW&heMYwFff?#<#~pm90JbV2#)cXLD$2IvKp4ED*yR~!Fxl!{DcXO2I6&L=Ta1$TpQ zak=j3>XRzCk)p!AM{#BuEAQJ}u$q&9M1`Ha;XA@+2pfr9JEhxt1gIe<pUcs{7Qt&K zj&7yN5!0P2!S;}R<cVV&-t|(u2buOf(0o36(%-=zLgkJe8NzGAtCb>@oYcj^+s);F zTh!35nU*IOyDgyAFUY`6CBqF2__4pA)=v$peiD1i1G`~Zpy~Di4gy?=9jpq)ixt^` zlIV<EqBe0u4VUV^iQ9EX?f#u8YvhS+T?<*vwoJ=!eHxjQ+n!go^W^yy<X}I$?p=JC zT#i;Xf0%C+RG<1>Kex|h5y|PZcU0@1{9><pFRl*Ob;maeAQmlx)&U<!8gON5$eJ|P z14g;zJ7kD~+o#o<Vv+SjhY@}_>2>4LF~>5#;lL)h<cBvp>GB5^hd`3m1$;>znd?68 zsPF31to4c@HvIc#Zh0{G{y1DsPR#NQvEf9pgs0!M=Ol=hN3fkfp}{X3YzT%6e2MjZ zkrK&gsrRs^6H-3(#3Dmxm@3pb{tACSO1=CmCj@z_8VK8$+>lGvcN-U7O?dcu4@gcv z;Am{gnS9r_x0>bOguhAEyckCLujTyrE2dYZC)B>eSdC;`cXUU|pMAt6E<7{t_yAYm zbqCw^V+2S@CM;Z0tR3G)(<N}THRGE%IJcXTDHozPrqs6n12D{kC-!3@I;1QiofLT6 z*N_K=VMRZ~(0?lR&l(kwUW!_>*=kxJm|eU1X%-s$_k<Ewu%Nvh5h5A9l7g9t7{v*d z$X;Krn*h-d9hI`N74jtNMN3*|#gq@KXdB;}T&b>=t72^9lCE;DOLEkM(k(5cdeGQ^ zH+^(nAkn8tkQzG^h|cwKCpMTqG`|lrN9-wBv;;7qGE_GUihrT65gqgX`}AFFBSj8v zgK6i4KFOK!m0*?`2{=-{`PfJzi3vh>ve=O{T+^yCHU@kN53s}r{R`=7JER8-ZZ8w> z2(!ID{GJD;^!JEX-@)>8A1WIu%7l-_WlS5Qm_hTc2jR#>Ykv|O(7)B-Nsb+?gV8z< z^(X48CeY0+Ty$_90ILE%=Hulk8_NE>$S0}-ryfk7+FK1XV|cM~-HqWOXNF+$brep0 z5GsA)*9H}Deu|wh7pA0&RA(hizPBl~lk##7tSC0>Dl9<Mw|sPe2<`3MNxp-<;V#NE zv(KS#1FNHw!lB8eqd3Trj+x?_<SXKd%13oyx^Izct<iFho4M~T;Lo`z@yJ<IqnTMc z;IX_XAcw;zRakR;vSWW&)!@*o+BWgXn?{0^Eg=hV^ciT?0l3hLgO1i^vI3Y`<N-1h zceHUPB8eKV+e~wr!jcUVRqgVgMQy6?JOHNv&XHRkWoAZqC4++{3uuvhU4!BK2OoG5 zF#S3KsAS}rD&z6rtHlNb^6f{!P_z)0u_lc?;?_i&Z8v-FZ3?K2G@`O#q}nk(3ja*U z)YgOjyX#mcI$_hqfYSP;R82UHDZOtRz|?!$L1R9H#h9T_y6e-vr4Q@UB^}2LNJ}2? zEdrrAgony7#i5v(zfPq{B5wi$8)d>WFIT!%2f?#M%86;Px@)q!4J})a(kG~`I<x&f zbc?Cfj<WNMQ!pcM79ypDKpn6KrKO)`AeCaJ3v;70YmPi)kAm=g0KdYkOOh$94*_fv zyQ3rYa$(8%3}qEo!FHAiB(O8=zS8WgR85n7iOSOMy27ol0jV{TMy`F9nw#@B6Y9$% zqwx0!&56p3PTf@UFmjSc@m2NvtC_|xdt<~`Fk3DI^}GWPyc2)|l5UO8J|!2V{`gQq ziEW&SVP_wYy~yi?_cSH2)s&zIbiF`Pdf)s+RQ0|;5I2MlvB0PLkUEBxn!jRADK#tu z_Q#UpO{H^arbzAKNf?pzNh`OQDqq~>@6YPC{wY$|Q|TpR_K0Ry2BmR<#>hv~%f@43 zWx`SbT;UM6v#LxaxPki4fd1mG&*l?EhsN!e*r$i}6{Nvik<>|}&^+u|4pP2~ysiP; zC;{Lg&!z(~ePjN$7ArGAuv#exui7BaQkcn1CMSUbP2E)3WfEWkiO``jl8gnh2A4Yn zYqj&`+r($XGOQcT$Y>()9t=}7EdVzIGnwugZfLyR8TmPVk|!LZ>`Vqjr4C_eM)C@Q zfGuwnuM<l@!PP<XpN0LomXD&rRV|PSVL-7q`7+-Hzg&rj(g=ZWsy*PC(01hbq>eWW z7C>DmUsV!Q7l2AVKk4=uK^Ap_L6ivsX9I!?RM!zIHBfEbwUd$Gzm89@%#Vc0JZndE z1kCzug2wM8)lqtqRvM!7DGaSi$*If&X0c72WaV3bMFN26IDrGIw8PBr-IZ=AipBB+ zVG%KfEQ{kafdNnnecq2sLV>)DW7*6pAcBQBJAU{E1kzhppPr<I`NL8Lf8$Hdunk1W zk{jWAaZ9<ZgJPwPf9tX-S*c3$VB+*EMXJWZcnh&ky6-*-=+;2YLQo2w0Zccfx00g> zrU1K|kwe08r!@)%@){UNVtq12N|7)pn%L4(9Dj!kRe?)7-E1jffIck?D3a-22Uu6Q zdKvqs>TyW;lFl6R86e~8e~Pab3F~El(^z@C)%zL6`Kut)pn=XoC+(uz)}K&lU!biU zDQc>G%fK6J)3gs2BHDrtAlHE)S`-8Nm4xMknky3iz<61OAqZ{-8XzhX_WlOOXPQKv zgc1d6QX~cju4Y{W!@%gvF<7y>C=AMNq8iUMPR0BH`$G-9T~rVS6P3tRzDuR4J&eZ` zf-cm=D!~7Wny8*C$Ou-UXHAiBLF$71rbZWRfxX}yk%I=K2N6px-9+VJb=Gp40YQXB z!HG@BNNgE3@!~ERxk5!^oB|{dE^3L5mKz@PS4wTPPyjl~Fc{PJMK}Xqv+AH)aESu% zvkEN}B+4j})JH{lrXoN|uY>IY4A(>J#1hA+zS^2iBTy1kAZiv6vPfQq$wMKF!}gcW z=;ZXgM!hPcAU)!M9-5dppxwb{-z#+o^}1m_v%!k;GU%cKHPWain*=_C#f*lrwU3T_ z1+QCv4Bap!Rfw>8!j8?L4@MML15#i&t!k{r`|5C--L}u`Y#&yerCqdWCPqxMVA4r~ zfi{R@6{o6(j%ufz*lssKXNP1yugU0{@Y?zvudzm^G7l_^AZD7WI1%46F?b<ciK=a~ z;@l(;A}f_?ElqpKW?O?&L~CWZkiMzSCz#r_d@vJTQhC$?zUmc4`zp6q<RZ^_!dDa# z{FYkDl3FIlrY+0rnMC_)kryp)C{uOU$hi(W;M}sFtpD)idd}cI?L_*GGf)mnPl(WT zF4&}9N=D-!imlIXvNN7W)yv8Dpy6&dlTfxO(zIl}P+vbXqh!%e2pCm+i5@9h2?IrJ zRlGPOcfz50xe-i{#>tdQuKK5zH}h=*2<V>PYPKX|jbEE>5+W~1{l8?xN>MK`p=Z8h zWT?(SM0iCTZQEOxs*UYy(=jw`2CM4Jyk`1!`%KIZ?r1#Rvim@8@081y^c&Z&mrtIz zx-%%OgrGXSe~o2P`v6iO4IZMv)8E5$$i=2_5Jo<S^&~PHcS+0$k65JJVR)^L=Euj~ zl>1oiMsE?fQh%}{Zd2f=r9&nL)(a)QG>OOvnc!E1|6IF2VN6#U2i+mGk*J-{Bo_(z z-vM<ZiLBy3r{v5HGZ4*qP~GUHINYnK$DG%LZ$dfiI9qu!gfBHk(r7>M)LmX4>H;4c z8ZA<lgJeOSBFPXJoPcU3hi;f%<>D446qyQ2{V^c5^Ju{O@<4$_{qUP8+MC7K?yRW1 zsUY1LICvwfR6f(H*U|=jKp|2;6{n_crBt}`&gW?X$vP5?b)s@lJZ>2H3pHFk2H0V# zY^6lnM{E(kekr*rKu8SL+w~YR4j3_V<w6kN-m>@<BViDG9+dMqk>C3NQlkNBO8<Mu zCRv0=Mb=Mw%%^R*k{DqSLZLFNn<p|2S#VoSok3+-9(r>jow3J)f~kCaIy6JJdl(Fa zP%-jS)#G6>mGHoU@6wl`YkRe1GE~ZLGi2EQ$nhTiNsJP}`&my{6;X1(v`)TjlNW*- z*>k_K*Ju1rf+vcg6uew<e6JFO^MbmJ6-&{0eZQlMu0@+q^&!uyrAjmapGQOlE*ZZk zq}VW85RqXzN|KNIQSJPvV4Qwub?PLp<@@3LzwT!JE)@{NMF0ZA6#E~J@0kB{e0QKT z=d}J0-S=ANTd;#$SyE3E0t$u1eg9%K)yCS2yQakCvJ8QCjt%$Ef|5#uT^Y0QCHX_M z{ri00Xx@Xg7f>P!g<v5*ch2CPFrIR6SMN~QF#%AT-z}haU-2-9P(GMqqk5j~uiA$L z5`6HDSu2-xO>2(TVOd(w_huBTedf!p9~zlox(xLRhMaq4t0dE_p}9HNzsHfSCQbwh zN0ykHq$47nYmw#UzTRz5Oo5kJ!SZ_Mr=Ysbr_Wz{r}(lTNKUmy9v7qlGqi0J;<Y&g zwy>YdW*_n=alN2jIs4!-6{&Yau3LyNJ6Du<Wq!Y7b^Y#>O)pgc%+D&9^$yiat<!DU z*)xV%vm~p-pI;sIoY?zO)vEEpistrOyT7fOzAq1>eEnG}vai%;f^G`6JbzytIE@fb zHvb#S5G2}pw0k@#ZdbPO(X^Sg5waO!V~z1g2pg-mk<F|z#=)3EW6%b?jUw9FC3&;+ z$3omEB$b}xEYx90IlZO?Z&u(9V_0gPiOgS}>ae-Xk2G)epl?NgxYSjw1S>K~R!gQ5 znzt{E9DlLARQjX9vnIavki})_ukkLn$z~>Vo{fasU#NNK_*MFEahCr9iYS_Eg08)K ze8c=aGS$T=5BX~?nZ~EIBKh33bglE?f7S9X-uzWfzfrv<5o6C%Z5kMQ$ac{Wx%+1q z`e(<|)vh8x=JF_H15MQl+@RK5@m_-@O)ZHDMvkC0b<;dINVkHoAY7-n!ut6Wy<X08 zF*nO#Pue4dtqtH!ZA}CE@4W>EjER;j8TM7z`tJLd?Sr15?4qit>mw%ed!;^}_pWR! zZJz$fI^?m_+vbhhWi(~a6vIBekp5@IaomSs;q&-a6Q);EH|F+>W_blK_jQ2zW#!yY zvs>rX(QmlBvnuDzw_(fbIv4RTi}^URJ^?mARGDudH$TT!yp7nN*H~15qsr;~+yPy# z@!&_Rl&wN76j4p~fq?F6+@2(ww1BM|6)jNC1xK}s2A2&?AFm8*JhdPLgxts&zQ7ZY z7kzG1sWLxjPE73w7NT>V&wh-VXI*4Qj#vsP(YwvT-`~7ZMYkl)ac?gx9e3hRrSb2( ztsE6_-|un$;01M=n1kJGY7&oXq7Q`}uSNdkp`*wZzDHXMy}5Q2PKzlB)mtx)4mmJC zZF1g!!e#E32+g9(|D8uVQ*K{+|K`W@x-DErsN!U|L_F)9BX9kypV9xXuaTWxNW<#| z&|z-QgB|D%<yQE%?EEm+rOA+Z@_xYB{k*#^dBd81!xK3ye;v$g4>qdQd&udv{ReZp zrc011ieEI9^QLCcV276@g1cujXsqGb?`jO3)13p?=PmkDolHpiM#1ERDdueG8!>8_ z(62*me3;;~0QuWgi`8|pg&%*gPeft1EL>jH!<6Ga)8}bHeoOpzUD=){Rwg&|pZNax zY;CV$SvR}4f!DZL>C@YWVzc)W4nn<UCSMn&wo;A$Y3^T$Hu2PNkg1g{-B7<Ez;LU) zy|~1u=@D#gwpioollp#U^)3fg2S-~>Up}8;`Hbj<ZaaAsIDmv##p8=0>uSE}XS*4P zPtXp3%&>aBi&i#3pl`tA3o`=fea?diHu#_>ZE&yddW;$HRsFonU%*0HLzzF3MM~)B z{S`LlBe=^gZ?7%-qMq_8ul1hC^C>*?^M&e}*u#IfbW@J_jmCZT&?&po_V{*OAuAP^ zgno+%s-!yMTW+zNyYubqMMKAbNaVV*icp$lfkV>Q-1+uS0V}mTvl7rqeM(_%OW~}t zNBRXv{zAAPRuN=wF_Lr9U+&=ifRlDV%CTCFT%(1AQFtYg6<S^*wxOhhc`X&vS}Pv? zKJiDa>-VL`SBtxV%<$UPli#}8x|#XRnp?Vb3%*@_SL`9mz=3~Stiub=@}Kb_I~)!g zhl}kU9ErC~wMIoMY3tLA)OG_KPwi@ov8ydYJ;b`#QlE~W?Mq4Om&+ry-mIR%>-h+l z?g-}{d-{&_Ia>(VSTW7Co1K3<QN^VlO}Ez07n8TR-)ot`AK#wuIxyBEw1VH%ma4Z# z-%NV0A40HJNiGxluV*@zaLfHx65Er9wP&f<r9>l*Zbi9r+F!~mUFW)mt6}Y;*_UOi z(9p`6=$x_7&rsC7*y$I)Fkkdy`w}tNn-}jw`J)pFC4DE#{cGRv$X*rYI=W4ldPb~v zbAMj$aj#H_JF!~V`M-~`TITqUra5T@3%J{U+O_Oe5R@zXOoUPOb~IBHF-3jLSn_7J za1uEu-IxS)HCd0h?pI}&^f1EwHPIhZK@G5;k(vEt?GiA6j%%9qw+>Yq5`VQ*J%hkJ zx(c>;DX!<kifA_^>obcR7WjpJ*XG=|CNoKw6Z5T#OjXg^MeYw-vyMk2ou$cJzg3y{ zISQ%k?jGjwg)%)Y4@U)VKVKzb$_dG1+4Fx-m$~d7-oZ}Q4iwzI)giD7!7Mwjm(zuZ zd1e$v@#xxx@zQKH>^7BK8lzkok4W+P)3xE5g*0O7(wgy0GjD~D<2>qC6CQfnsl=~E zRugO;0@q8=Nu`f5a1*BT0_Z9&N4Sc-X$9`&l$^}tg;H?Mc2P<!yD)9>E=1rhS>sb8 z9Au5M%ZD3H@JC7u)FmlS716H(coS?Q#uKKg+7cT>4J9K>b=YX89yJ4S!+)*Y&eD&J zDa+F&f9E8e@yn|Zkl~yA0w>*^4Xofolrx`%5J;)8Jdz!MHB*R#6luLQM^_FkSKqr; zLyeqU|2;Ysa_YX{&Mo#hnT;FvL)lhU54dMRan@nIwIqRIb)7jb(E|_RjuBZ+sp%Sw zaJ!&9G!BXcnVtEZlYE%I7C)^khnTTd_xoq8076g8A&%qB1coulf=)f08^y|EWJ$He z8ezJ!z@mEGUZ&AP<)#WFFm$xgr09RfkmZ;~A7E<;=Gt`BHJB<{uU5j+Na<|kV79uj z7~LX6^UYC@$QD#RcTXfg>lA5(RK8UTCJ3y^A4fDxvk**M0UPAaF$lp6i+U}Aq-x!* z$U}=2H>$Sso3G|bJ0#xJDX4gNugo@Zq^7ii=O-ulJA!GXWRlK|iB!@Vb8Pw`TZ7G6 zXRugohlg-AMA-_&t9(qNU`=^yMTp!Tsvw$+4p`1o<jD9Sc1jm0XhOzCLA^D;FCkKt zx7`#c%-9gO3YC+PbU!`uys%&bHfz}k#2l^@NDEr2%Mv|9l&=$HxO7ulLL{aI{xsEk zy$;F&b)r+_&#MB=C%-2M<|-_E!<THjnOU-HoM<A1YX2~FQ_ChVNBLouq-C{&%B_o> zOFd(wMA0_y&@dJG;EF18D1a_Ob9euoa_}vLejzn63VZ-V)2MHol9J}WaMg#23A5HV zY!pxI`uaCw%CmJAZ^YXqWencxM{688hv(s-R9~&mXH%xm-+a2go_24Q8~;BGJ{TmU z_0UBTngew*kgUg4CI<y$GEp5N%*#7;peV^DPP#&M_;UCIN2x=$fO=Sj)N@9-thN?l zjKI9?iFz9&eVoh%7wG20cf=LMe;7|><9&TXamE}TxZ#03j8xETWUQ4<e}SsnRNyj@ zG$MoLP>xni)#h(0Aj8ZsSY>WFAyMaIwt_&4;mq~|P=;xA;qmKHG-tv>vE72{SV^2G zdHM3<hA7!H2IlibVoj4WTn1t888b|Y{QH&=md_YvNig<^-h;vtT!z?A9a+E-GX7*} zC<Z$XkgR-Ks%?t68D+1)Z8Jxuc%@Y?8WYR5VZ4B_HLF}hl5G(*+LAECZgg&Rl}BQ{ zLSOCoJN4x)2`%76zG(`fnUb||-H3u=Z504$Yc37e5j6_Pu+V+}(!4Ywfs3=0+u~wu zNrKVOpYConV9xF)L*yY8-q7U(CpY#TI`|bn{+edQwL*@)0TZfkIIR*w+u{`Afy#rd z>WDRzz@A{dVklj!mWe@Vu))+2#m<N7wbR65bqdQO){0U<%YcxHUq{oFM4c^Ba`gZU zL7o-obfgV~TBK!k1CTj|Q<hVV`en$vID{-qK%*I+mdoDicj16Kqz&XMw-0i0G(d?$ zAVolIu%xs7U9+lju2xzB49pb?-4Hkw5AT~#r^g%DnKr-=>;S=5IC{(ve#Hf<X@b2| za|lunDU{`v0g1ueu*gOV%Q|{gaEcSlt`En~f-3`A_P+O&kPC2eFx@;6jp!}*au3G- zoynOUpaor#MmmX3o)!-m=P+jOy^yhql|^+1Bm#q#3Kh+>W#tQpOQ2a|`k=GGL2h{D zXk+@8S{9Qfl{QKj2|+4`T_R=Ut$7&Y*#|mT{{{-U4f4P^k-#&xC<LYLj6%R;=`D!g z7SM3Ludj0+rk+MK25EMb#utpzB13r4-G~fcaVR8bCkigLH1h^l2AM8BoD0H0(220# zTR?|ipKE7rAS{FRr`E-%HRGj|yAIT}V(>jm7X=@3(=CR!3ly~!2Eipm-qcKPR8n6w zOa<NpAv4&e|5?q%A!{{}9lUE#wlfMn8hTG2pra{KZZ=(?(7)Y>2zHEA=C~FQ)YymA zi+4-h3?4xPDC(opEmn+G=58B7_3mP6wTA(w6+%Y%5PTY$hI$oDYn>3Q1SbjGcgO2B z#DobMg3dO>3{e)e>i6OJLgfceG0k*|52>M}K(V0dgl<LTlHK8R5>HdgW|?KjmO_+V zfePH~&BD9E>LLi<+36q7_-&^nqj%QoCF3-U{Q{>3B~%%QSYP`Swi+cgbpr&=?}Io3 zOTD&vyrKG|nLY~M)^>D$5dPPZvWb4)weDvv92(~TFjBJoXQZssSXbH?L-LhZ{SurI z216Td*CtCxdjY8c-#6$JDpAr7=4olBwnmdQGyJCLNuZaWi5@0cWr9t)0kT-w1S|tK zxxOwv38>{cCVnS2sR{#B!O4GNi;p!`(?yOIlu`BMe0%_I(Z^x4T_u!uibv56r0-g| zs3RZB=SByxbBlDc#0|xqm?)d#Cvi=f{s?V&L<)EeEWkH1iYpd6Z57!BXBBe*x1lCZ zDJEExWTg_fljuzDY9SSN#K4V|1G(O7CLhVHDdrX;wJx#@a8Bpec{%Z97N8v;>JMGF zsEVsXSj5_|pTTPeP>z#~->{>NRO}t+i0Z|AzHaR#+dkSY<nRzW-OU7j+|cOl2=kMu z@;T&IfYDQyJLV>cR#2rW28j<z!;z;}JQQ*0@q)j!Y4GLXxjW!d2e)uVCwi;4Syz^t zt<+R=x0q_DmTA|ll`HT<Rg@lc#u}dHnxEB>D*Hwb4@D=eW|pwSH(IuPO}NPb;>YM5 zSaQ&VrY+gvH(g>%twDoj+$DuHxksFICYoJvi-UGQTgI=}ms!n|tg~8@W43#y-7{s3 zCzIz*9X~mw+Jf0)U-47+akIs@Z_%-rB|x0JbB`zy+D<n@?$-fXS2gwI`m5eSgXet1 zIUF!OsW^&Vwc%ZYvmJ<hufdx*VbKB*0z?J50qL+y)GnBFSOAvzQHbTfE6Y(Q?T%mD ze7HFN6*u5BL+63{teFWM$HXSu9F;d{s#Q{zyj6-5oF+{&>#VdLC9N)v&~-o;8^D`8 zM(pW~#!IXz4X|}X50giC;nF*~1x|EcU$3G>VE6$w7f)TT35y~N+WAR_EVd(IOcspQ zZs7I<8|#FHaF&=P?|wKxy}46-bWz;Jdi+SLW+%H>_|uUTB8*8c7FVp3td80ZPbl({ z)XB1xPjkt$E(3Q5gbKI@mRO^Eb{3l5S=2<Q9iGz?z$?#oEs^ZwB&h;cA;uInZ+rLm zib3;0Izggu(%JZl-6qlsT6~n>AJsra)W_JFJk)+DRt+6~`U0LzB3;6ogKwaKKfuRO zEP-Ky$SpMVCrAng4*`qVJTOq*{bNCn^KU8|xQZSbLf!A5Yi~R&n!mR=SFoo9z9N0g z5{GqQ-QbB32fr@f-VzV#LIdyvfj_PZhx1ggrs2Z%Z#@9Vn-t=4j+<Gy(ZkD<9}Q#s z<W2mb%SV;WaFp6?%$cnXJ@2=$E_2f~k)Z@=h!7&D!q0kx5~c97$!=<Hfj)jM0fW+S zv+eA9F%oJ%gF3q7^8pNf*G%!VOX|q9zihlkJO=_|+e^$<C(?s~!)@oXOnx@Q(3@g6 zdZg&emwJ>dEx-VmNc$_da=zOOeTAc^>CjPdhW`oqQ|a<#D0W0yTWc3qDs@UxLNuPu zV_1?ub?mww6oDpiZF;D~flc3URF}@bCqtFRP2#eWUPII+-WPN6OR`ZZR|zKhqFp2o z%Zyo~^OA*S6^T7TUEcO<Y;57(8o9(JHTki?I14TIjmED;0SKl@Qr6a0G0*@;XYuR_ zr%hyR;hKrm!O4R=aq&DKi`+Olg{qsN!7rqMgtPPYxyed><!PTt5xj5nuiBdrGfm=z zBf(X_jPPv~MEc#+=a#AaiF?p`7J)DMkg`*Qi`TjPIFQ;7cq}v|2H-DTa;u;JG-&se z5kL}Tu9mO|NXsW0g)V;c9wv9AAEtiX9cV<F2mG0tqW00$6b7j0PhrvUz4y2pbI>Yq zUTn{T)GjIo3s!n@m_;Q2tbTOSFS-24o<6-{1phx#`mR&lNY{F0TkH?PXByg-=Dgk0 z20<`}hEzEXxhlWY>pjh^+O4^ak_%Q1tfv`P$`BS)Tj+Bd+M`>RllB=@|5@Zqlzfxl z{_}BcfS?_L_YijnK%~{R-DB6Rj1k!y&H>M+v@oG6xizQsp`t6>nU<mzkN?|jGQ?-T zNR?iuwK}i}b1dK_Onr|-0Cj>rGEsjvAWCt<Ke)L9=_)_PqFr`&sWfGECGh?w!2&mB z*9@m##!_>s!Y1HJ7fw3qtUJJ#nQH0f%GGBAyWEy)>{SQp)gbjjhry@q%nO>zKfx;7 zzO;&dE>%)avCab5VcAv(F<MyR+-BN)1!GOV9OLySi(3fJU2BXbggo5xAPx@IGP(A> z9d<HPwwxpx3tR7?gt}8#x=$~8xC@LBdeo2=0=9dfd2)+O)#XDeQwNP$uW*|mg*4XM zx0G2LfGw=z2Wvo3ZSUa2jRu&{0v%m7nK+-spw5{6wl$+~r5|qbr2uUwEXz|c{L~&u z)NC>IbcTOSU3l*k5(#fIMXW;Gbs92Mqsu$PB(S{W9WjZGx&Y@Bsb+lxF6dR`k`>Aa zF1S&5ms?I*bJkcmn_le}9T~qPrH!@Hnk&U@TBOEe1%0cAQ(+-dNU5wSEk4i~eQmNT zC(6S4Z88xsWeS?#MLV(Oo7QYN!hKyaqSY4Ax@Kv=F*RXSiTFLBJMDeFRyF%5Sa~tv z!c<yGd^z?}WJ#Z)y9*+?CFG~~D@^O*Tu}Q_rQ}(rhf5e@bvET?0Mt8x6LUn$4J~Hf z>L?D|jIEgaWLlyJEUM>5)Z;$?&GP*~|GzrVi|Ciz{o_2#|KvRD|8f2&cq)eWwVlx& z+#d&n74O<poR07Uk{jS?E7<SCl_9FPE@e_pH3oJk<{QJOIddg-H=YVDGhsrcm9w5Z z;XXY#W-mV>;WcQ9k@%0+I+9A717VaU`q<VpWhPcv^X>BCI8UxdF?GL5cn@#HlMGlp zyZKNwm;740nnq)XbUfJ<tC-|>qYm_FgQ!^}LvQs6Qb?<dDnMHrZJ6oUH_y&732cPO zU2L9ooH?}kUPvSjtdg`G{8z=Mlw*h!1G{#J<ZyA<3<}y#OLj5G!0GQ)d3iF}<ejP) zYWQ~B)x?rtT6^m)JY<!4(yOfuh!{$Vi9qJqlBSmNu4<Zcso)vI*lTy2#oXRpW;%JC z%g2%DFFX~@L8zu<{&Kz9QOQ*(n(Z*gJY#vkIT%g;5{k{ejg82AKDv0#_bHOVuisHb z^N-EzYA>NHFS0D(@HpiCSie!BxXDbVJf}M1hBv^1rw(|)SsZc5q=#WzcCwXyl$mkG z7iJbUOykI=cM?0M`nfSpwd{yBJ#RmZa8V~gss`{1H_MDX$P7RT{oPK@=oeP4Z&`KA zkY4=<oaJMF%hNwerHZy3*B|0-ah`r&jvwcU;Wx5uvPJ}bb^k~s?a+IT&<CnbXE!sv z>3b=lvg#cIcflu;Ljl)~fuXpTrD%Wh%=bzNIq(S)mMj3X(QHalB~_<Y)B#tPeiSIc z)_MCp1Cn|$p6>HrMfve}_?Z8YKLV_JDOm(N?U!EIeQHf%>Y`=JJN%g|#wtv4hPh>n zm3PGz2hYg{sP4DZwtn5ZO(xX=W`6XQ()1oAQK=Jrit{S2j?P<2{_naJOa2u!%F@CD zi`XO1)JmMv5^TD(HvPMnhu{HZsRSp<`D=4UjyU$K5mU!-mUYO&NHpH9o&ie&2nwUb z*vH`1?q7D|YkIqyelhc$qJo*Iky5hgemt5W0XVOP{}^c}*_~fWJeg-W$AG`KCe`l6 z5{5uzWF5d37j=AtZBU8^A=7r4zQLyvBy#~UsTJg>c|D}PCa1)rjpUX{kdY@f+v*B= zaLU&|X?eLmn+D1`Fq*(6LB^JH`V({o!-oKL9G+AnpO}60{bF#c`Y_^Ku238N2VB?( z3;SS?YYY1ZpjYdsE-f5q>U5j{f{4%8sVVeI$VgysQmC6Ae+&PmHf~rvpCN#4p8EhC zuZXPuFqW+S-ic?HITAn)UT3{_nz7hsZrWli^6N@`W>%g%ih=4ff02hoiSX@(N{F~R z?x?Aa34HJM6$XX=N*Hz9i}jb~7qGK4r8I0mSWlF|60w%R%2mW&EHJOD`f|lGqc|wS zhN(MB_1A(g-B~(#)rmLpkZ(?*B9w+MNA}wkmk;{Wc5U0Bm*A~v(;nY~2)7ueCRt~1 zJ7uPHXjyT#g!ElRNp?$KS`I8Ria^(Kv-_?|JIvj#)oXxqf!&pY<v@)f?7YujoI93A zL<&q>|6|Q8XqY7|iUT{mEZ3m@cx5>w;!yF~XEO{cmu}?KEU;X}&rg6o3JgGhcB=}8 z+bP5u5NoX6+^cm8JorwSemnU1a3fEh=R=JsgE3oc#?U8=@((Kf^cO(nSv=M-UM<!a zy3M00oGD@!{`Md+@8g@~K%2J6rnL8;?Kq2t!2fpJD7pp{>s#(Wz06l0tPf*zkl~m< zQM2vs#D7TRxx%Gy@l^%+m>R&efF$fshc)riyV<9`z&O8vtm^D{jsOvU5=&bO1G&-G z40^cJk)QJ<W02ZuJWUg$^dS!Q0}Wn;n!4qFi*jrlM=d|KudAyc&@cQ@S9OdVcc)h- zv^rDtowRLg#J0m^Pynw(!;o8@tcIT4;1NV$T&i|z_yM14X_UVhLA9boT~3p_X!GfT zM1q74hXQ5yyU{|clfxDwGYKPpcC&d1-1a&I2d>sfJ@_IN94cO8Wyxi9<FN%oUPY$9 z`c$ZDkaB}TMcma^o0)YaA0!_P1M4{Qv1TnwWic>xjM-p_X;npuj09?rH=NF;VxOz- zLo*hd*;ZCzRSXTOur1S0JFL>vNV2-jReFwR>PBX>PIXF^mk)DUHpp|?O?IYE<;8p% z2U2~g@PsP1#IprfC3iQ#Q<g1|Vnua#q(NuK*U+(vXW|J@!&Y@sSvGF00*^#b6mL>? z$bn{t^|2o*sb2M}e77$a`r}(04|3vl$|iL;TOQp<CSV|pF`xKwu3AERV(2{;C4DBV zXoPiP4@`$2PPL$V7;?fU5y&N4;Wj3~v#D+KHm5$jKo4JFnry4{bnUS4sl{5I8+#-} ziz`A7oXb&jA%*N*8kLP-P-SutpK1zzfm`Vs(i$aaqe4yNJxH0B?;>?k`5uSlCj_5P z<Cx1g09g^!!cT&>0#gPp>n=g@!I}dgExpBCYAtnc37yky^pFg!%T;T7ezkqYIg-!& z>8b!$8LF_Npk%}_;;VkA8ZAwuG>!UUzo-4*R@KBx`v|Tpt8ukPFZZ>j-RY4-BOv@+ zFzr!IcO&`IqgvZ<F8nlG22)}!$uszBD9Sqf_o(*hjP;;Gl%KN2L=DYj?uojkgwvYj z%K&d@z9ZJYgw%F8pyj6o6YgCr2%%kao_^?1|HY`&SK<4?_l^F)s?SMZ*_Z#L{u{>s zFx|8L_vv1mES>fSTm^Qx9=t<^Dh21DzFJqaNP-FWUHmo1N7KuU#M5NrLJ1l%Kx~uO zmv^%o@I3RQe*b^e|NZ}{kN<zv_ws+bmoH<dm~dPpb&TETNeZBy=DH)ENlH?qhQ;vs z!(@sLK{+-`qYlzwldwty=WtF5wo9wK_^^Z$T0iM2KTm6yxPx^tEzvIGSsL%67jcqc z&>Uhd7-7|_gjayKFoj{5$Y@Ef83eMCkc4K=;oE<=dTJ7gl8x?9TDzP;tBF%c+I>Fy zoV0cE>LH_MFnenn>F9F-4u*E$a78sS){qb27=aiNZR&@A*x}K<(??O*XCkc>Nv#>c zlD|uXl7rL42~lkFySsJP<ITx&b3~IIh~<IVmv3;{PH+`v#+Po^Rc3!|xx#X)6npF~ zXBkqZol<jcuC9pPaucnH+hx&bU{YpO5eMK;G`J`)MrYPh<4t377BQn~E5qxuvZ^o2 zOfZQ$E|VZNH^NP@;f>T=O5icn{%(XtR2^C~vsPK&wb2Qb?Y{RQFf2`>io_K$?B{KA zoxJ`(*blJ(2m6axr(9BwofhGn-jIoodq;bHZlu}^e!-(hf+9OK2z{6sip$G#{)hLx zkGL&|p1@zE@gPW&6y^WhdYJ!f{p0ftXzIp~^`FYhFUm19{?k3E&{`#wf0*cmS^l^6 znLpNt|HpbkDXrAf_8P<VmJ)6txZY{r+56h3Pw+-FWz}CjK)NpK;l-4G|G=)Csg5hI zb@g6<w4Y7c^sA#(k`awR6)?&wHRBGm!?i74^c!2BM??^(5FJOC?8y<|<=Lu>o1nUH zl>C|Ri%)L9c;)UH1dx+lstB7N;2pW=pKxaGaFeGg-{!Iv@?msuAr~MF{@>bzj{!ga zf3??R`=huQEbWl-4Pu@~LT?E!y_obcvyEcV>XK2o+ITP&NZfwjioQTTu;85>Rwc}k zV<W`^ESqnt5KVgq>jkEQ#cL1>5l0HOz2h;&VHqe@l_1e6_iMXL&^VCb(iZ6&|56r% z$bYI^UD~(?yaOU1(#YS@+k46>&8Sxo8sy{pdX3Qa>Iwu>|HftgxQjtJP7#pBC<a*P z@LD~(HCihOw0>U%4@;(9!=BQf+YUYf=dy4|7AyIg@54lJ1itk|C5j*CvY%Ak0zLfd zg9f7@XFFT<WF<2Fd;c@v2mQ?V#PQ7$7)UMLg`LI<K<jhOXRecT0if=yIU?2kk9v_@ zX28^Mxe~VuWfo{4T9^oBevJtJQ166GI!0ajFDRubWOAsFSS2PIHz4OF7If&jiB80{ zW2mW4Ykqp}bP*Cjk9td6p-HDyqv4f@zvV*P+r&!{e430bd2dI!(WnL`Lh<^ttsf$O zn@!r`WST^14_B5lUJsI-egDUL(a5*iK-mZ(=cjHZBA~z@>r1_UtpBdIc5%1WDe|~_ zOh;je*pd}_Y^UhIMJox7tQEIHGNKSgu-+oqs<xI{$HuL|gs)eMt3ZP918~4T8HN=& zi0(^yLh60Dv){FUZT^$rhK>%h4+U`7Y7bV2k^Mj)`vZMI=RJfp!Ep@cdN(ET$I~ry z?`Jr}5y&cUZSM&F1>E@s1Wo6#^9`5ql^EtyCf`dn4WkuLPuU3#Di-?9>w|L<8lR&O ze_)?w$k7Y_=Mab1A@st9y2jwjF1_McWyL@7!;a;j=<H)T-!R+RDqI;XRyxQ^3{08L z(L9)$Rc?Hw)wvpH+pG-(y7d8}QaDx0Ws34r+MJ!28x9EyCOq1on{S#-4Ekw~`0SRQ zh^u_s>3?18tA}UcV=(V<K$Ueqr}5?Ihp@Tj$wsg9p|cBNRwFK|!p!R}R)9lz6u&^n z8jb4;TJg}5N?QSGM|Kp{gBt{)WoB7tR@p^(?PXPm6ryq)n8;PP&6a(w_L|kJY4m9} zS<oep#b(>7g_kpx<v+3gHCHXPxXJd*sqB83u$cu-vH(|BFY?WuHCd^^@)o=G8koBP zwJIyemnHOIDtO)I*_g2ko~9|ax01_~(!Usy4)9d&3l;t&JfGjyfm=>~+*zg^^sQ1j z#2hvjXXuozK{JpJ8dh|f+ENB|>?x%?x42f_KecZtfA?B&2&+mZK?E3uhK6VMW;Z5Z zAT;De`0@Cs$+Wgi+}hl_rY@`ojBGlN>GI$Go>Hi6{1nR5MR6;#aLe6`x7<?+xWZLq zYSkteaR#cYax>F#4E5UvP5#_Jfb+@QvONAO_@~ApYf&#OG*^3?T|*LcTo=un)0h(} z7ds@ah4<K0m@|Dn%FZDB7E)Obold2uDlGY@9_ik4Q&f<RwKSP_k2URcVCeCvBbt_b z%R`-cArib3Am@B;r-#Aq`|=ZB-tt;@3$>F|7sV}AX3&MSls?K6d%xX-IEK2zxVwj7 zTqx9s;%cCl{xpBH#ZKH{nFxn`aXSm}GscrVmE^b_{mX8;vW#+-@BQm@OZzMIx_ce+ z5pl)*8w3dG`zOq)APowJ1_S{F_2c|6ApM>~XwaWWxS!gV|KG|M_W!s(skv^qE`j2! zZ}1&>V<hPPOWC&vEETewJm4~?K3V1{;rAQ{V<Lq@6Z2~AHb8ccm{e;ZJkb0COm6h7 zLMg9CxonM`|AQ|NoL;h9s&R;@7#TX)gmIeebk7or4>N9r49<_^^O#@mYEL!c0CTla z0%M4(w_(T%NiNFhwP0I6rfiHyPxRg;+r09kK4N&aGmn?AbiepJvEMTfdHn63LpS7b z4e@pQ%qD|o=b3Th*s(sc;luKBj|rE51{p&x;_P^ghA#ffDw9rqYdw6CiGV!VA%nb< z#9y)>khII9M^^bbvjaFJN;Y&LoJpDqz8T^1{S!s<FWqz|i3pCbnl4veluf^<v}D(h z`$82fx|<$(F&rj%-6a7o>YACykO!^?uv+!Bt$62M|9X8oCd!lr{v44&*B#b}+4C69 zG}UY%get4DVz2SJR~_p*m~t4|8x2LrTAB5kEf@K>)fgFB28K4eE(k{I+>9{k!BVTL zh$*yOOv#dxd4B2AuJJbaA?RH21oSIh#K~PVgZ>X1d89U3bBYuLELvU7q=EPD>Yiol zOOwRr_9KmMs&~`92Y#{LXMENCH>vw-@f0dU+w|RmCF8B(R|(-}tGwmI?b+4Y(f#Vc z;qK;5Pm%m{(Li|Cy)c=n{pWMJJI&afB5ykA>qo1)yW3uO>n1#ZuC5+F7r^ym>+-l` zLVkHUobM>DaeV;v`H48o^tDg1_K=dBqhFo3f!}4|^dO5nm^3---xetiS%$ywY|*84 z_`XM~&d5IT#Pa%8@9H4E?#7UO6k5tf)K$Lt*JoIx<<pe6C7z@@I=d&2q++*v)XTD_ zk{z!{DRRZoRld8g8+WIbi1#RrirwMOaZ`Ft<@E&7hTChHZPt30=F^nDzb6gB=Q7#A zorVozxR_BbftY6}5OwpxzRbtEX?C!2&$UenGuGaXS|z?}44bBh3N<dxm*c|d4v(j# zS&_}F7g5V|kzK%Hbrd_?-IA4k;9=V3ob|?CYWTd#zEOHKehxQVr(}bt(!qA$m_B|! zE=1>y&Yvpx(&6am3~o-_neatVo4R3NHl4u<w)ebTz^#3n=SP$0YrHS)Pr{haTT)Cj z1GW0FiHww?p&@cV$`RBmm7pOK=WmNt`?3cqZt>UniHc)shGDdX&uyh~MOPH7G{J;~ zWz=wQh7if+wlVCR#Px$o&i?0d_`P8Y$+~Pqu5ykzRpybHbUz8Gx^!Q3Ik^C_9E<VN zUZ`5+@^YT7Tcwrd@t0!JzRrb~?v<AAtN2$E-n?F~H!$V5$RLs^2PW934CBqsRqTTV zqP0YvNV4@n1Y0E5-)3(=G>CH2>lObRDzFjh*22p7*3N72;Oih{M@!n~H47H=eog%B z2&*9q(`cLuOXFJVDwh0y@!ufjSOYew)S{t-f+kB0bhs3quxcxEqX_Uo@uf|}HGzKu zB97u|@bX{{`sEa17=y1WXh^F-6)w92^~smL6;Ws@D5+w8iR|w{7rAJns_Hrmk)ov* zN)=L_kt+6?KraqsnU))jQ#Oa)&8cYER--H^We4Dtbiv9X1N9UB3Wz8eF97a!ILQab zhA9_<PYwVS)BaL(KvNFs6f`FSt*&Z)E806li4f7<y8u_X0*WvUlO%>lNJE~@L%N8^ zL-$KmurMb3*Vlt%s<bCwWNWGuTx<Bv=iwMBg^V)UJ7*6BR=%PIQAc8&MTH7>eJfxg z`wyJdEx(7ZMscw^_t~gWi($9iiwv$$BcH@Ecpd_^iM<zCO{8@{CZn)FB>!6jEvHyS z%Py1DlthbWjiDkoLQJw7QAEiy)C!|UtJmD`dM=R|JgDyELP|>LhNS^S6;Uhy1T-gt z-pX}z?p`m#1y;nO0m2pdZ{T=<*<az}jD8|>ut6B(OC-(7QZyrA0ii}4!D8q@<8nB< z5)c0+<tq>DWdp&D*y(5#x#t~$z{4B@2V&y>ml&!b7gQ~;CFYpU4m8y?34G8?@YyDy zlXe6S+!0yIE{r7f>%hC@|HaoiFlV}TVLBaK9oy>INhclKwr$&H$LhG_<c;k&wr$%s zCg;pdP1Vdd-(PsDp1s#v_qDIZSi&!aZ9^n~Pge-@lH5<A5x~4IDDRl5rHy4jRs00! zK9y^V7W~_oQ=1%VAyzp=|JLZG6Jq`Ex7OW$+1~&uvN=a1zD?+QOo7#yT>oG4qCwQo ziR36K+B-Dg;Vn<t?jm#hB%|T@kzuE8BRpW%zSaoEkAbI?I0BMhU#IpaVg&e6M!T{& zm8BO@z+;;+D${<bL_{%tJ0cgBOocr|E$+KS>NyjMT_~P&Bw6e5US%{l0{&<x>Q35D z+O3?(b4vo-JH^LN=i)GTJMvh{1A;J|J9BemP9%j&$AUxSC9Ll(13KMo5Js}8hQLR$ z>+LARxto1QZ+(?r51nxW8gg@TU#RAKQ||0TASZ&b(6Aa$GqeMQsRAN}=hdaHalo|e z>H>nXJozNyl$Z|@FfSF<Y%@ywIH|E~@NUtXYphKdraLe&D4`eO8GE;=9M&?u`$Xyp zr(zythwPqQ1&g9iZ3@<Y%ebfrxLwlV$;(Tp6U#)4wJLk4d@}2^it~%sY<J}j=vMKg z0M@rC1Sq;3tuA_a!lYaWx_?|4uDEp#$Vig5t#9KmJ0KF%b2TS3Z6t}w%bGVg18OkF z-8g=(ZGK`m@&3kJ6<q}OCM%N;uV2G^PrKTw>U^6yvVv2)SO$*w=)Luz&V9VNqAZ$x z6g9s@=po!^0B5-0`TpZbV3S+~Y2j-mp!DB@0M7q=B(P0vNc|NASTj<8jRcqmx&w7+ zk|hytmKG|@70CXCyeasS>ZN33OMC_XZOT1hxe9KR$6igce!jZ>Gv6m}!pU?rl$14k zB*aY>YL7-v4Sjb;W+u%x{oKCx7lE4bCZKUrJHjj_wEtj;BJtTg-rGj4lCLb!V<hGJ zgDEMliqD)QMts2|Le!oQNZ-)-OBK&``Dz{ZZ{Y8gP$3`xgG?)l<b_PTYU?ZTU&;C> z@Na%*mUN;x_xTF^-S+=y;P36qt&{gp;6HrnW+Ig`U><VWdIOc<qibxOCHb6M0-ajB zci7>g8>b?X>o8O4&38VTBWL--$N4@i(mp`xE1n*$|4Rc_R}sWfqHuJEO7g1SJz~2r zuFwE&FryfR5A%L@*#c>>hV^13k?NUB>ai>LD0I7|G2bJ`w55^G4M!O%T2f7W{+?&G zMtU~*Q#<}di1i0Q7qaP(qSKOWEAC2wWu<9^u^Oi)Eu5$sb>o41wn1s-sb$P=xHBng z<dJkQGw=G9w936Q)Erpv3|y>oA#rwHLyrsZz`!@_+bqf`EEeUma6>FH>ZYj)CSbmU zchv0>gSU(+T>{9`;Vn-;@gjaE6pi*q*5(6JgWFDW5M_ABhtXP8{*_Zi_{@eh<gWe| z)6-Tgk`@*-C^5)DSW+1T0HGCN9%_LHZM>VYb;V=6U9pzH;Aal&XZcU?uf`*A%S!4n zVG=%KzcHeWrje-n^|NjW#}}rhTM5zPAPaE+MjV--n?kBY4Eo~vam$eP<n^3W9aH!! z^>JtpEw}V^A(C^Ax<+jMn&=eaMRWp&*qtY*h~U5Bq<W}dyWY8peNQ6Zk2Lnn8k{Cs zo{@{5boC}GgP%_9^~yw72sflS7WP}5Zt6RSSw+dlT%^*PdJ!`cRABR`dE=+yw@3%i zYt~9nvpawle=*I5Q{;d4V$tfwF+qbfv5Yhts(d)Ry|ws?{iOo|PneuuWIn?uwaR=F zpKo*mGAZ;dtbQqPWXi%$@2#P4#%%WlCMc&U@ZW!ADe;5K1T8Ev19PSLL#M$~%&*kZ zx2-bEu_<NbBN|X64ypUv_^f57c7y14f_8(BQTObYkZ=sf6soOq@BA+rY;8qp3C$5W zOCmu&Uo#%O65E>kw#|4}8$9?%rj6Df4lweUWl(`<H%~C<(_Ia(C!j(@Dd*7x73P-) zZ-NUVcG&&sh?+<P!Z?CZdV}Hx2vdRVYX8m!Lc63wr#jtqWh7MJ0=cwDX1;NBU(Uo& zt#A;~NiF%t{+k2nEm|Jbs1pD#$nrjx{HSl!xXRDIQUIaEJQe6%dIZ^XQ+yw#bH63~ zi08lrDJ;n}PMJQY;USuRn1#tHK^i)8bYabSN?P>#_jX%t<aoh(uVr;iS<};MtSYrw z&hIw|1S(i3@udVcL|w(*=LF`3{G~}$RP2JWoTirN#ecU$n`3zVG)d**i@S49sS3Xr zhKI%22J)@!J|!y$7-EfU)dFH+!4tPAv>wfdp~>NZll)tId$u$^r+=|1S(1{G^&XcQ zd*YJXyPgRxHf+mQIou;i3T)NVvoYRCmhcX@iO1OLgB6gJHvaNq-BbE9G`HbTxq;^E zn{0ovtH}h`%2=Ef?TtsOMkVcEF8GV<3d}auvdEY5T}mn}pfC*P(1k}~!CE&(O@w<% zENMH)Yn|nW<=6lbh$~<whGz1Tx@{b70LuH<>l{sp+}|l9{j~I4h>w5|L%ggL@S7*N za<j9m!R99km9^cI_0u`Abk8+B8!d;%)FpCw1>$o5LWRbBOX%;EaN|zq9dR|t5R4N# zNEef0VNj&z12Bc0oC_HsMU2_E;i}47O=ZO4wHc9YFfk>BNh`(;bR1X~M!$X{C`FKh zMk+t~5loI?nys=O*Ml(}Tdr=CxT=XFG_rS22{iYnfbVOnA3MuC*3JAw1ynk>ZfQFx zhxL!JTF(}o;^@E-o1|c2M&1i`{4D)?8Lg$pMq7n)OBD7<-g<|fe!Q&DG&xHxDIh;@ zi8kxR3#SW6DaOuD3(AP!P}Lu2(Vk>g9(2ObIH~^)6KW`R&nnfxD#U?{O96~}o>b%k zIk&7l?MA!c)D=>MiHrnA9j&uj0)t0M<eb5UJ*#ar>)EmDVn4YlD50w#RbB3P>T<S3 z2o@+gpJqJBW_tfzrGKB_G5s~^pg;$htF~Wa{xMgejnK)^V-TV}*Obs@y>Y?oD`W&) z64J3#HX^cPQ+-`TJ(mtc!$-y#)#xVLsjeEd!0|J#E+W0DRL922PN_|I4GXvFl;Bto z2?*Qs;0DWysV43#<>Lf*pFQ<DLN7Ad*6PNdP`we8mU6H3T1f^!o|;x}AK%fXR$c*P zAZ>2&<2Qx#G1K4us`E@+?ux_e-G;0>mEVX3`%>w6<JEcN%ts@Oj0KQ)Lb&{M!Fe>M zDpHc?QN=Q(kju({BpcPMTh8ocD>*hz&W^4J+E%P8(z?9vS=}xUzO^lees0sBR=Ay; z9zFQ0J{WK-&eA157;of44d38kLhoSg+~U-fiSY3_o2aEgPTu9S3CAITLHfD-hm3yn zZsJU-eVi`;VfGO{KNe#TrNeO?ws*qdEx*BQ?u*U$`gPy`zk>t-x8+&sS2aNJ->Lzw z|GOIaU-%!%KlooI+gCsUmn>=Xf2x7^{5R1~T3(g}o>~)in(c2>C2hr3#ceJxtGCQa z+4c#LWR}C}c_XMX-pmMj9#Xm{bw*+-zCT}|i)XEpOgSwQsd^PsB)c#Q={(|1t|jv0 zU*Ot>OKaiZf(wjOsDEj&4_RY8HD9CoUxij$dKyIpiXXI74r6dcIDk8vk!j@7s)#dB zia3V@HG3L}gd1>S5EUR#ilT^Q|I{b4ivF@4b_i7ZB9@-doNM`+WN(3rz}bZV84%bE z8`Xj;k>v>gf|ijNMb$VdK37SPH<8VEI7#v5I3CN6<r+1M-+v;}-rl7lb(c?RPsMU> zkshK+DHK57mUuwzi6EBhAc9|%Bk-LVDXyDkM-;MHN)QupW{`5D(@@ivazT*#O52>Q z2jg(j%fd1xo9NUyRAy`N)~7`B<hoh47-^&&8j32oLTy<Qdgt)t&tRc$MFx(f`<Yn> zot2wPmy2wgN!I<J(D(6(X8B8OIM}|ITMeK6FCf5Ns^#fD!}@TVr%#$%C20XJDDBnk zG)+%|i(r@aDW+<;UKBO$_MYc=&7@4&Q~-Ty)8Xz!rz?@}vQHq-F;D0@;}!4#=N}-z z?a1Y_L+*Xs7ePpn=-Y)R^PDoZdc~9ugpza}KN0SR`|IJaQ9BdqPM-y|59RIK>^crm z0XQ~dgAtSP0sD;}73DgbikoTyI*RxTO&K8;ZJh>f6+7nMW+?8B`OYT&1?w`J0!)zT zi=*)ysHsBdFCJk2Tu;{{<bNjwwdoOv&<%8}4JzvmteR$Y9>6V|Bd9RaB%=LDvdzg} zeFB@Me|UhoQ>eU~e|Z22j3+ANe|P}V!YzMCu@%HC+BVVQOwcX-z2H2{P|}a)TJR;- z8{zYTdiu@r{rIPK9!xT@D=!YMeq0X>I3l)gVr4a-`}<jmU_&JBz^FG&&Q&sB2}xy| zoC3d(6p;ABQSkBXLf^d~d0z~mfX_b+phQ<X`0Q|z@b@Y3R!EVh_Rd@x{*Y-f)vscJ z|B5X}H98Taq|Y;=?-Y$0unC}>%I`qf>B#RG2HmS#ex$J&YtUZ=x#K_kDh42F-KZhY z(22S}KmNXJ?AtO_f1=AZ`E0-_-eE0tU>mV^MD(QEWcvzdu-e-ITf5?FJy`NRe!R3{ zcJlwQVF;|A3R2yO6X!MG1|Ub&6Qs{BjL;)bGN{J6D<Hi*E&yO8YGcy?QW`kO6i-=D zwW!m0J+5NT>k4kH6_)r;tXo|=&XfwLuM<+bgS7e{tKX>!%Fr4F`A^rj=j;}?kqkb2 z)1}HW(R6xvdipH#2=aqzjdb<1z{d;MloUk^$US-#MOP<JCm>LmayLd-+U%Kio|o+# zy!#^LIowL7{<;nX;^nyS;;-y87iyR2uY&OI#A!5Ugqq^*nBJh4$p|v#xB68CrpYGf z{c?8wE~uC@Ql$*HWxxoau0|X|jt)ExT-TujLmPj^F9MH=v7^lMv_ac3kJQ(ule|t& zKZ3wINifp>%uc!Z5)jbJD@)*b7vk6vvj(m@oLu%g3k5_&{<(R0Sr;&yv-4`&rT3v^ zgpI+FwR=712}jQiFzP`OZK6rsw*fZo32eI8oR@&E*bxBvNuRKUwTape(J4221ZyU| z_=mq`3qfNQExyz-Bl>Sf?)-!Q-DI-_5QFB({e%D2Q90vam5Q3j{EPo7Wb(851=0P+ z|3EbbmPI-#w|iy1$ScK`f!S7M-}ATmD~yEQQ@f3r8p`Oe94wc94cXCI5XK?Z=|ne( zT}l*Jbh%A@u+QtvihXGvruk9eJ|}xDK6c6>4P|k!vTXqf^d??#bNu9;xdJCWD_|hB z<>uT4=bj=#lr$82)6T^5J6ATiGAcIQGD`F1MZ-6~wOyP_<w*PN1!Xa|tF4w=DVidO zL^~CtYR&+(os#MSg;eyw$cw*emHDTk>-Ex$`bskK3#Y{EkP<X!QRzjXg}Uf{Y=P8^ zfequ+%8N4<Bbvi@hA6D)P)jOxr5Qyh=@dEh9Lk^yGV2aDtD)<&rDQ<PeB4M#&F%?( z4QV!0onrzNtUe2EHR*iDpyTc(Ni*eK{lTU8Pn)Rs{Wnu~DF;>B99Dk&G%ey4`GiO# za6F83yQxl;HXc)4X*)<7V0nUVb=Lrs{{ZxU4n&&JX&YWSvq}1=`x9rXxm(sW|GjU1 zfCQuGqcx<nU6JnJt^<y+1hx9y_U2Gev6YS*%xQ&+Gyh~crv0K`$!hPiO|RhO1Jp$F zi3H3if`;jnuwcOzPeUqXBXO!PN84;g;RD`7L#tDx>-09Wp9EZfx>EhV2i-Fcq-?A` zI@>qd{>U<F$}a$by<(MmsAvGKR;1*h)=HqJsCvUExyEGRwXzIKqsG-u)52C~+{&;R z>WlkZ_~QQPC&!6@TmrwizgLp;{&ps}{C>6xWMewxLpDQy5kBI2<0JlcvzK>l#WlyM zFeofK>nXcvA!;s|B08>PIqWmG;Gai3;y@TozwOnl2Z4Li$7SGuZ1;^gwBl30F8I#> zLI5!TyWG7TYxRr!yX#<ihYAUcWF*o8WF#9hZ^4;^4)g~GA3HNgwKpaYX{sc^?8Uy| zzSm~2C+{WGB{C`z_^<OmzD{4i7Mm3CB(TC+tf{zERM3zi9(_T7Ixb((A9vZ!$KIk| zOqG2~V!haIW}in9h-RkC{6Em2<`?uAM?O*i1^uz+*J;BY)ry$aF%o@2f8kB?T4ELC zh(;<~ol;HJ7J`PkKl6`K5C4eHdI;pn1<j)_fyOtkOG*SgA^!{gm3EAx;>A^_f6;@- zqzZB|lyI4G<xc?j_e#ss5om^C8?|?Ff}~lp2thn3VU_msrV+<hT3wfKiz=5C@5{3& zAbyqpu@iadTsNSwnnULLTq@T&-{ZD<3IW0@^u1})H;2%+@G97$3iZF9_o%C6DF=B` zMMg>hF}+WTWos@f1%Jdj;;l$Vr|?HFN-A*Xe7>1r#q7JDH5!oivg2c)7;%nc;*HI5 zV{|QLA<iJ^xLNhli*xGjL1dn*x1Q!U21!HBchOtOf1hAm3AH$}S*_K^tOrYE_Jy(; zPY6LaAD?lg?e_hC&YCT@g~2GSRm2|MBfMmGs##s4vl&PGCR=$_AAmY^|5b&H6R|=q z20$J*6?44%yjP4lUO@{i`h|NcV0qJ#8CEaub`KGH-|%_nze5Hm34+*PF9xr&HAmS? zs-ubP=Ue%GvTiw4tWVkHxX5H$x_>BrfQeOu%UC4A;M6jMHnE~7mLmez?f6HTf~{xI zu<&~sZQ!`%zk}$g=U(cay~0(F7nLYF3m6FC?<w;I23B+BJ272NFuWy95f6>D0N2Sh zVujcFhvszFUEz&!Nl+i9)-XK17zd~q=*?f=Sz!;aLwz0H*+-!YB9VDlfTUljALmX| z%+xBz3Q-fOHN!A1u;(}v{TIe}O)U=Y8>DaqHjA^-JzI6nPMp!=VSvMgIbz}Eyg8c{ zngT6)DNF=WTYBB(C<T+OHzN%7u3pdC^d475q@s)B$Gcd7XL)9^`$K`dL+<dwixI^0 z@Q)LGS)mQQW@&b?bbqobI`t5`8@LrPW30BV2*|-&aJ_i<ekP4Tn)Wa%5;@|H4g;e= zf(sjvOZ*EN41T}gooaAK#1Lj+g;3spu0B4)ERpm69ktNmK)(~e`5G@cRB!TGn~cX^ zcs^kn1`cq!s@)?G+`%XfC&=>iVir(hEmjtcrrM6MX%?OR4iT(m$7)Z;yb)y0N17hy z^4P=j8b`>R_hnd61_Hs&4bSu`)=&Q6X81w~+rZ5bQY!*F$tBy^$e3>Klb9lv$=%l; zWa*sRpTmlNCw!P5WB97|yx2RPh|}WW1u0MJP<$${my7C-om)?Mrl_*Asry*P&VRze zPmA3v(NYv0%V@uTCC3F{U3U67@!S*MstP!cS(Uh0)lsMfNe`zmDQtGrAK@1fesw~X zf$55IiS@00NMt+Q=b;8Qp&@}J9yq;nF@@cm07e6+0$QYu6*4ncsB1V6&}7C;wgOec zL#)s)m$D1lcqzQej$fF8ePT`DHQEVc#CiHHh<8R2aYMnG6_q(Ize}ySSiHtZrMZ8- z=~*94P$COTq+JS&2t{W&&<*{02bua_vv_M~2YDtVm?TZRzTw%?<I}JLx~Qy-ZaiN! ztYz1XcCq2U#IB$*QAOCvfBlI(5J+@*E6CImvTuNYDv!-Dj2At5>Yh(-MGCT1A<bFT zzNK{c2L>K-&-tY=J~EB(Z(A1@+kn$e==%;<#SJ9$6Rx%K+f5VIXGet#;%HOqU!=PS z1D_I`q`H=7dVOzL{nH=Owb+W^)2c<ZEp2c$_VIf2y64Jk{JP-FR9e+UHCT)~?)Nyw z(FAaaP>(+~Sm_94Elu{DAsDPFZD79St&{XkOyL^p4!J9muULWZI+sTFhRTsX@Dc<G zls3I5M5_+YtIV{Cbk4|6aDtBGo=O$J7?=JqhT>H2J6oQ9M@IF5u(0-Lr(1Q62jGUL z1xg8;0H&lD*{stV|C%fhC9_!YCTOK^1c+fCL%Z^S|AWBXEpPR+$bw50O0`Y18di7B zV~ax~Ll8RjBt6EU3B|nku!OnvgtU1dr}enUm)dx{o+fH2Z*-PDkT;&Gg0+(TB}U-o zbM>=*|IL$qRGdB(>}l^?ZI_7<M-~5mHweAdunB?`Y){$m4-`C0pH>nIu6*Y}X?)iy zvH%>$fquiuLR(uk!}%X#IXSNJ{E2KfigjyMy2Yjqk`Al}H~4dYc>0CV8G49Yx`-^@ zg*$5?9i(%<)$7#(fD?-b>8c0xN)s2$4c&tNz04`{;aRh%vij*pv84I39&^P<JO$P1 z^o!+-X?LVA^*^s(_ENYyPpxrD7`m0AuUbc7&2+J#uufT`BL-W+RHeHnhj{LI61BxB zoBEV3y(_}-ou%$T3PIJL$`50n8n+V@+iTQst+S?8xi%ds53Z25h@v8=#!j@Wr|P)q zdYn+#JCS=>JE%LCu(xG>s_&I63SX}>UVp(0ShwzP6CffExe1Yw`(VnpS)Ry*l#&rx zcN=Y5j{>jGU0?G)P+s=_^YotFB{vod0tAHgYlqbTFZgHvcYoBFnwHawD4Oq=0VXJY zOc@z>Jr0?Z0vjIGYd{{#7*{u4p$`-Xu7U(+(6;!q!_19bJ-}G03F#hA_c*g|^Law$ zFehiB^C~=VsaHTtnOp=-eru-%lXi87LSrsH@9KGWk}nG>i&H=W6AA`4K^>%l$TkW< z1~X~Q<*H_W-Z!)2M2fd7Lek<+x7*+26#sS<;P4dz$mssmC1PH$JL1O;qXKDz!>h<5 zOR=w)mbt5L)97u`7q%Eexyb_G8u-BwUD{B`>8w46(F@&A<te;Vd^<b>qmcOltJcw2 z5*7H47l`IV4!fx9PcdAFB=@zi?a@2u=gJZFc1%;iqfb!yAHLJ^$~=H))xx=}CtFA5 zNkdd+(cE2(t;<|!>6}&t5JUj;xs%Z{ibIzXJ>;jLEnxcVvH!X<TAc+>p8YCDm{4;Y zohtnlD!5#^{;!IWbgD!6F<1nU6CW@ZL;Xgxt&I#yz+B*pvWf_RgH>i)h7QW$#>85U z2AuzQK<P(iR@`lLmYIF!O&5K9lQ#KWHHppo4Ej8yi#TrRZ>K$+HZ5kfVRP!xzqZ%L zH^K0*PhjC!O-wce!6c;~^iIl*+OIEJ`p2Y@Da;!+KS_VPT_(9@0b4G;h~fU$esYHN z@)6`Oy@DZJ^Rz`9`Cq4p=Gmsx39%rE2B)A@Gki#%aXAF$tSl7FnU7Rz=x6k?K`086 zn=SiFeu(U)J*<*Kz3fwGM)64DB_lym5hfRtUC#6^FY<BQvE;s%LQTd_ZqRLk{%FAq zeL2wpOCMExmo%FLOf3TG6Jr}vK&xbzm*svn9=CW(=9J0YBm#_2QCh7oGdAye9|HjK zo3`cgHZte000r`5`-r!xfxm}}+dmmTxNFGV8b0<rHki?}koYmfCZ`(}tmOpzD`1VO zpnI*&p>^;=IP=DJP=gWg<jSz%Ec9}u+6{8=w&^`;4+u^LTB;G=5J=9mcL2ARk1?P2 z-~U2H>QB`?wz$HvS@t$aq1D{vg8OAXoL|rD>;L|d1h624<r##Yo7I}D%rZkmn10dn z)T!)^BX+*7hCe_Xeua4dk_<^%nIqW#+|>>F@LFuCY;Ut)3o{Ak=x&el!Rr8ImvDva zu_Nnn&kdd7a!c&-7XET<=u9Lby>_9ts^Paq1=25ttsCfBf|fCk3}<FGBYU_(BsxX= zL95L+me?!R=YE=Yq$*!P!}+M=vUm1|wF-3vB^N30?5IRs08l#=VX%3YTk;87NxQ)h zN+vbQ{)~cXBs+!NeO`&m3PA$q>0O(uT?+$(#Cv+&2H7Pic7H<ABrq2qcy~rzaT`Nu zBU29OZ)br8FH%Kh_XlZX8zi(?D06y>rS17x)YpdC1XKQa+r_Hsf{8`WhnSDbG%2N( zaK}{@cnh~4d!rV|kd)ITpJ81*Z;a@rLo<FbbIQTBMQ=!1POEXTO)c~fU`#TYYQ8eL z7o#;91CUcCsVXf-uZOCF8-)d+@lyxCd;@<Hwy;E)RmK%vpE`Bf5J5~+!0j2!d+nme zJ)K8d&YIN*PBwd?+)vy@)1-nfY{GJK<SR<I+bt`;lIVzbpRXdk+V2+MS<n9EEOa#} zAf)p|p=c~gb)2E%cTq-;;-r3`+H%ZX5T(Yu<qOI0kayHxXooNL0Es}T$O_omdb=HU zt4N~7EVw&5w10rp8*}`S`kdYTkKNGuuCEWu*YRoc-;U3J{rC0iT6Q~}Xuf(DpF)iQ zm7|gj&N$=}NwFj;0nk1{$)9onYer<Y2vSPtmFIgWYs#&-`X8>YhNV;o+MT!0+R}Ev zfT005ffhT7CHXiwis*PA#^++IF|BeOevQiex93&BHR*M-{(7-IG%<@jf6XN4Ii2)@ z#nx>1pLnei7%v3mFv|t$VuD%L?44ovn1s=%F2-~BQ}?7QN4m2K>|di!SB~IN+jR3_ zTbA!WJLf|7aH8@`#hms^{uyU)oZAbPiA(qPV!x?n8GX7>&c_`O2RMg+B|8A~HOqF| z`-%F2ipdd#+a@m<U9ug2X@vNo3)G;1MT3i9Pa_dQO%@ameeVir05OrQy^IyAyH{yt zy2!5yQN*+61O#pLQM@mw&Z(msjmv@vz1AEGbsM&T5h1ZGS(9s&ho&~xR1P`|Hj@^Z zYqHEC+hq#om;<NLk?MR$@SHf)<ojEYIGt3C{IYylvSQD5b6ovza_k)NZ=9s(&OdL7 z?HKHL{CbsYE35k^&J-xVVMDlPu*ce5*vCznGvcrDIb*)#%vf7pXpi`&RS>{H@B8~T ztynvB>~|e=M|D>ZI2wvmZHS&GoUF<GB_PVspif#u@53}EtO*=3>2G1i;p$OwQ*6^W zcWjCJ!J36ZYhP|VhJaN(5nW3KH~QBWd|v_PGBz+0jUqL*C5{sh$!eF}?{bm8DsqY| z9&@=ecMW?ob7kkFGPF`(N4H^2%6;#<zVBsrfcEzM!K8)rs6rE8oZOD(4dZ!iSnmqT zHx_07E^6Mh1%@)7QK%=oOw`MkjJ~Ds4Q4|E{1v=Er{1sh>%M*Kb{8-V4q76vxx8jn z^SycWzP=N=b=|h>s{h9IgDr+2V)N+l2$G%Tc$>01oJx^Ll!|BYK=6%f3AH5V%Xc;D zq;j}Qw&g2uyO~m{rt)ufnN#vQ$l0GEqDT=ig`|Hq7|G161ggzW*Wy{px0fv^tY}=g zO9#<4k7}mT5k;wTHV^paY}bZpe#yl51W_J3thtG@4I#42z?LFW{IWh!jO7i{Hoxdm z2|fItFo~!QJsk_mv&Ct%f7S1`ju&~J-D*V$c|+v*2~%CNsrVhwm2y;o*iwN+%2lIl z*ZjAh2+c^z=gR8_3t{_@)EAzX7G%N<DJn*+H)}+`uG}ytAd%j(R$BX2>4uY1JNeuo zjCX46Su*ZfKP|Qwcr9VA_c5(;v6Ezv`W*Y}Lib5G>zc0Wj;FH43>Ex@q>v{hE<LH( zn|*v_pB6^QtU@8L$y?vR2vfMVujL`qZEOtCGy2g-lc~~xgN0mauu}QWAJ$@Jy?Bq{ zuvfv7idlem&`dbs8}0?zqqsMl-p|xl0_nBrN+sVEd$S>&42K(WADK++!XU1tO+}84 zj$M7>qbA!eyTaUG)vmD;;Yu$>DTt1uOWHJ)AJNssdJVv}_^RYr1y@q4r1$76)US~L zkK!LQRkk=grJF!}5+a;^?cMfnh`S|TxR{%EW_jEOEz!mkj8s0d6dU3`sXcY&tv?~@ zUX`C_lO@whDK=>$iy(#aW0bzQCO~<e(0S$+nS6sD7i{cvSVY$21q2TJ7A!|eU--1= zS%37W=Qun7Xz!odd;U-zI|7)Rc&ykkaHdt8X`xM-I8%s}&*R)J%*=Y&A$LA6%*6Na zOz--<4I@)Dwaqj(lVy8G-{+r?LTdhe9(=7B`qBLU<s1Beb0csGl({`H5Rgfj|5Atk zYa)zRT~AnHN9(+l-V|8Y9V#fd5l<XHqs?&Wq8$VIyR;kvh*^X#ucY%Z7E0+<$RTA= zeP1FnNGN5{_jaCen|Pjx^?p2Nlj<g@0acm_!e0#miBGF!T7KB7T+{Xo-kN>?I!R^n z^MuRgBqGE$Kx{=L38Y7ufi9UOvho<{uAxSvaIMtmX{Lzl*7nhvya!vdi|5W+v_0T7 zt6B@yMO-9!ZbBLu$ssb!je%HG?x_w<czy3%j7g&8NKNAQy5jY#B(a8bL0m!*hF;*8 zmw{v>3JUK**SS!eLdXP;mE^HRY5IYIH*t=`2;f4QMa50X9VvrNuWi31l^9RExhSNd z2_WR~Y?gwM^g8;xYJcU^@jb6oo|=pvH|83pmHV34qEY~y5-Hp1znkQ9p=>RV#Bb{d zU$IjczPJp|=~2HY|K<C8&4jQhF7)CvYJz#Eg1A@|;J99WHN-B$Otc{_marCJwuPva z`GeV|;x7lxTWI#phf@ax%@v=y<YI$W1$1O%<cw1b=;evXcWtS2Ckqux9S(0gIqwl~ zFuuE<5Wf1pYQ}VoM)P7Wy6PiMQZ5tictF{slhrCMMH8P397D{rcigsgM<M#l=#>Qz zw3uod5sD1p{lkVYA`RbuW<qF2bEKioFD<qY&7IWmjS<$W%N2iwD{a|s9e4M+WU7o- zm@-QNP!4!m&8mE!TPPOgVOi8t0Q?m5nNB!6Qr#&hEWDmAS!N(jIv^f*pp7MPxK`U* zbx7!V{>?zKIB)R6%2&N>Ot9&X-UOoN3srPq2JSQBII<^MCm`+yq0SQd9AmQiiGCXW zxx#%H?Mc0nFnQO2y}iXB3SE(t|5Ui#g>;M<fxg7^eDn6WAB3H?#k*62qId4S9JyXR z$Q&``$`f~k&_+OSEHXgG<boJ3qwo-&rp)S&s`YeS-=n>{Z*-MhwIYx1wkP*4W@o6A zg;upv=`))ZRyzxd7FCM6+JeK;`G$FyfqY~%Sa^@2mTx2Mkjek$dE_~z(XHvHN36>} zC|jsnxDo;LD$Jm5iE;jGn|M9-Qd=EpkK(Sh2`jp>h8d(9s}AsSnYWC+YR%qiw1QtP z6J=366%(H87=Fu~rd7in1l^x^B?%8e3rMJfCL?j-Onz%J{G(S)*-D5!E<cQj9G%L0 zWC=c5(kxRbmaieq5)T(YW*#?XHxAvBDf~?SobwY82(MmWfhZDSiU$9j+Drdicq<R0 zhahu?7q%3(CO_n<mmqGfwEPD&iW9>0J0EiUhu8tVe!G=E7TIxy`$Q)xpN=qZSrzW! z8%^6M(tkP|iW6A@EMIUs68?WV8~#<woN0_Zu5zMw+Rb+c4QR0?;faCrgAy?-6roAP zsG$GUmAGCirWOgMQ>9WAnaOs~w%XL!v&ID{{>D7cnZ3SxLX_HN>gM-;>yqgEdfMc& z)|#q8P$;q4tFzZ&Tq|9bzt?!Vj?PgfRCRya2K+ofKPF)?W|vMBq!8H%Tuo@dtcWzc zEm|B+{n4Vkx9(CuHpA0F-8$;LE@fw%S9rEAmfJ3dOwn;qBFW=(nQ=C~ek@9n`$#dq z5JhrXv}{vom_&cMppTJ6v@mMiPSv&Ujc#+JxSHWzr#{lzG;B;?=XKoS^;3(Eow@3x zks`lStYhKL08Ejv_Re;<;g@JcCOQ&RCh|c;Og?9$@5FwS;p6T4>fh+y1&z(0u?bRD zE`J5dg`so7$%zZcD0L^}TWJc#F^PM6iMoE7jbSdOywJ7Ms}^FNym%2--D5|O+gyLg zUayRlB#7#Io*VL$PSvtgz+^cDo1TKy9Q3Jj-a~dNwUOPdG{~h#&0He1-mKzXRbv#l zH7Z?ixF;B#77ar_CsZ3a+btc-=i9gN)X9h8dE*}9a4Zel=~LAKpjIT90F+Y?<f|OL zoxmOiVa}z-jNvg)e#cz?MHbWBF~Fk(svarB^JT?O9_)@j+a=by4zhE6s`6w(+J-5x zsih2fxTSwwx9!$0{O!HPafI1ldDOnXn9o)Kv`StNJ7}TbQ;mRRoVYp{yyK6{tso=0 z)F=^TMN<;W9vqa5menCmQOhXWAudvR{^in1FA7(tCE{-ofj`t-nyW#?$6H*a)vg{@ zEe&n%$QfhclUX&{Cn&uHOrZSo{QWabbLr&GpjA~`1!G{+;aV!8(H|>-%deBpcRGLz z_cz1pk-6eKFaFrU?(y=L^V$U-emj?c)kVPfEx!$c(qv=jnxVXeSIKw8efU|<ans1K zeX^F6O-UHhQo<#dZ)}PDFFC{>@cmCZE+)8aX{%qI<|AG=s%g-c`#VG$=ZG532!bOa zCKB_{WTGq;8B6kG;eiZ~)`*n-!YHcGcW+ORd7a7yzV{%=;M6|WS(`lZPZnwPH0r68 z6U?zXI9|r>v9XXS5$|GZSf`&`hZ?OLM~?D?yi*20aQZ?{+B8LaIXl|upAFb^KGG)Q z$5J)w6>M|?p0#g}K-6U#I%mJR;Ie4V3bHGiQ#sS!<2%Ev>)U3Zw`1##Uunau%8kF| z5V^BReVDcTrc-GHJ^je8b*ejPxEMFxG|HZkwE?Jw7S5^pk&F*we|x(dXlupVySnbG zdENP}CjHN(V2_*;5g<AgF++1qA0TAdWtH?|2=vigkGx_<>vm(MZd~;%qXW$lf~Dk@ z)4|U9Ep61QNQ&uhCWt*mbibW{+s_hS+}Gy}M9rj*OwWR!7__uay9RMCbd-#Z1G^kq znw{03)P0oHSW~dKn1STbCL?S_Fwv`iv7q6wASlI=GhT=^0r<-%NUFR2Q6Y;>5m0}+ zV^;qfOM75@CkdH#q*oNsw%!%n1mp877@8{W#+W3X1)5q>tElbo*(L}t{Ro}T({XTx zuUJaj`+izjhM%8*)2FZ_eef>o6sU*`@21KzkpBzu;i?h(6WHdIUG%8vH2mwK>Lfw- zFkeb0;{+N*mTFxrimGq-l$8!cPRB~u=o83s*8ItEj<68-Of>uT$)Kv(TEH(F54JSQ z2#8%imnuBIWCP=^x63r|`42oAUALt;2$k8)Ir-KOZEYnK>+P2^Cgn7C*_KV!`zhF- z#6rJd{t!Y2vbdWzsNjOa3KH~u2r=;hcKu<=>sHn4v$oe$A8+K0BIYz;1!))&z)_NJ zbLR{f3MiyRO>?(hghmAWs0Y>#sn8~Rk6$H=CXDjUn&*WWDWX}xV~>50tjF^qdB=y| zKgn-Gp+AssAbA<Y2<C{wc<TJah#w;uK!NOsYe<4)LiZHGJ8_&IO9$=UMnRdOco5%s zmcX%SqM6$gnz$&bh=N~~i#Ife5gZ$DA;O}Q?&mX|G=nk?W))e<(zxpF%Cdyin?cN= zlZYLJ;q7HPzX!gA8)7`!+QZw^GqP4~p{)dvs11Oa`P1<KDrPmF8kL>J+9GBJt;gVm zE1hAeOn0cmS&I{yRS4(TJ*UN53p9Y8X4;f3g0~+5hv|u9Bw=(^AMV2F6GO(kJ)ip> zfN$S-L?0Rx9jYVRflW~r0K?P#!)gGpACf|-2Py^DzW^Cd=rCZli|nsRr#!W5voE-L zA|C~Ct%Bq8m6f0qi;H}0Fe!{=>zC6^lgI|LKu|k%AVvSAzyvZSF%sxDTn`g<R4vzG z9wZm++}g_ZAEhRWW0}N3{=un2-!3qH-uc9sY&z$-YV#WDg%9u`S3gg*^l~MD81ms{ ziJNs5gQ*;U-wrV?D;-W4y!#mTKxdUIEFb(Ip+`s_LH=0`D5YX<cyB$B-_|ZImxF+@ z)e6Hd)TX{sS4&yeO0CGUK4oL<xdg2CE-^9?*O|4^H4)>el$1V%Z-Ak(hZTPj9_W7C z!5gbYgzgg8ddJ))Pk7H@L3j3^XxIP<5|VNohX&VlUW6|_(54vDXXh2lnM@KK1u`LU zzy#q^Y^6rI4Qm;W7@!4{V==t`>v~tE54*BRyW*~N`Z%siBKHb=OejW(5N1gaA9eBB zt4CxOw^bAWl|pX`A9-?#)qcI~dIo*Nz2q-9LHbC!D{})BHMb+q3Uc)o7cJA3CmID4 zENeH`JfsEw4|Y|lb<1nT%$2E&2DvKl7O+^xgPed^q_t1W&eyL><3F?^AD;ksRCo{& zIR5`qYW!=p+0$D8M~MDU8xk-)p`1Xf^!B;@yN*<<wxZMoke9inhOZe&UP&!DN=UgO zYv#Esc4SkPp3xb+Ss*t9=AXnLuWBr|5rMBu#RP+w>16)_;_d!=Tl|M%Etm1ezFI9_ zq?9CAKX_1=aT!2%JzuGU;_Lf3$k%?}q7dDKfoO=<7Z0)?XkNwqd-pl^w<v3R-XGiu z7CW>ZXubX&=t-QXlM&p%1HYmAa3jfn{Qh$afxhrKSUrV1sQ}_!WzXrfE8e$FMm{}| zZw%QBiZW~-JmhAi&yntV8?N}ul-@>Kul6U5N^l#YtljHWdYvnB#xKOyt13?|c>5er z6F-#mk6N%11HY{IX)kUA37&f=@XadxSAP_6qr--H6I1qq#)pBk3i>i4+q!p#d|p*X zf|*VU0Bz=#tXnlR#MZggd<|uXCud<LQBNl`z26T)UJx6{@n-;+gKr=o;xM6PIkNm{ zRDk)Vr&~9qwuUr<n*sWC!dPQ7>$+hDh%N2im$pAl@U)R>p`_`P71IwS$Q^n`xW8%+ zp1D$bW4o!jupZNqNK%RNv#$Q#5jl&^9u_nqkj%*+{w@v<aLwF)mTxb)_6BA0rQc5u zLp^noE9;$jz7lHU<talf4o}mnm`JJ$f1&AKbN*p^@$C`(0lFby_VLO*EgoPNU5+}6 z8&pum6Rl0tcOnfs-p-VC0;`#f3+laP6TrB^TXNwRc32!l!xqN<+)*;W|5B;FYVeyY z6=#c#D8AHOdTu@I_$i;IpYYi1Pfqges9yLbCLdl~cfo4WWmV65&^L4-uJrQ+(TxiZ z%SO=YZ4TaEzJj)G^7;_W+Vd0wC6$Xxr`aLoj>hYZ-Sfr~cXAljUi~JQL8F7>xXe1r z&7ly_qiFrL7sHE{pS>?}Oi`U0A69}THP1ap_GV}lR?U-CNsJ{ZOrIbF+?*g>m=oTN zKMkzlcToIuEEo$~{HWk+FhjwiLIK8LPW*u;WMil&Qjgha60{t$Iw`cCQCjs*w|6op zRgz^G3`C+@%Fa=z6{KtHiMw9lFIR?mIdX_rw;Unb=+DPkKaX$I>8By#c2Xs+BB+Rn zyL|atlEIEcj~ePi^bgKp2>|-hym>D)Pqk?EN~zV~IzwcO=9#i2W|a`TJDm;Mgb+p? z+7Fig?3gqz`kae?j#(tcp!=R*TKTP>xt=Dw?5yRru$3TX%66X);fysoo*dE5ir1%) zb)eCum;5%!?(&1pl!FhnhPJ&&#KNLe(B24szr+)o#z9vHd`JANKIO$*y8Erl==(w< z+}@f4v*%0n?_e(MVJ~xI?`%nz-c6zW?e)VS;>*>dekFr^auv)<Hu>ggo=%E^wtU`> zeIX7vyvt`Wk$gK>FMV0@bcr^Y7w#0%@gR`6#2g;_o|0U<UOuM^4XbuBgVpAdja4HQ zg;Bw_Ich_Y8XwqY(-QPxXuY<(_Ye2b%6>A)H6zfJ(-&?=>EqX9aIMu&xPlP(;FX<3 zGHS>4*yB-#jipyNbSV`x*D8eUc-dXga#v@}1)^}Ttc+c~CvRq0-!$FXr9c@vU3TKE zA8dG88j3E~{lLl;Ig9*j*2p?v-)_(|RzTG*t@WDlt}kg`AJZ!+<vtk)Mmc6wKdoO? zJs#xyef(=VBbT#@G3|l(IQ3zsoMb<5wJP_$+;YsGwpTAEm7|L7JHIIrN@r?u-ugx& z{LJ-w%)vG$;+*5pIr#Mu+1M!H@hN@pwHLbUdbCai7317wS5AjH1wvr#T$@hlRlX<J z0~I=9R;J~N{AI@SO8M*dNrszp)*Q%>k-6=^IEpE^%Xy=%zSgDutiT?T*J8A=`FizR zUyLfzB%oeScI-gu1};h>GTS`~;~0cNJz|)gEye48saJlN^lDUWnON@q%bOHW(0nMn zF$uB;zRk?*5|(dy1sAwVgYdU@qHQhG=x{PUqGgkinY}p3=ddIx6?jf4o?g~?zOK5e z?A9>R*=beUSZ{^h+PXPyBQYHfR1)Ik0po<^_<M>4g@Bh2gO{%)^aGzOERw2FrR*4O zKT(NY<kdc?V5u-{w{(H@w=2Lu;y9~uGG;Q9Mx<-bM9R_)(aLKtb;}d0wdn8MrHzG| z40RE-ChCxa(`Kn-2X4izv%ls!!uvunJ2*gPRibVF#3JYJGE``2ilSWV!MFKH69DFE z9TwBiwh`c9p~a2N!T8+OR~jFd^L+b2rw{SXp^m^w6~y?Om7W>rak~|^qaUvGlgqo9 z=Lii;nXU);u{X#k(STq0P;|CS_7M@OvpQ{Qr{4&2=)CuvdINwc(QEO*t#Vkhe2F|H z2f1E0uLrE0Kk?KVIo>7N6={jZyf&ocxWaytXh49!4s2Z8vWHouegtAf8j<OH!ji6a zL6(8I+`#ucXT=_H-V8lU`wMo93Bt#*dkXdoj{E3hFlsBDnD=(TLjURb-g?ORyn5Sw z@7i})8|iN4WdI|JvcXI_ArLS!nr@1^E0m<kT;=!+2mM;%bdKFch!a|GFHnW8p*j}3 z@(9X?mUP+V3vfEQ)~JGI?AAT>xov*&+{D+B1A)bjwt!;fG9;)aneQf9%`zDUl7T4X zo2!E+6#lkyQL36xt&f3OfTy~;ncm5MwnL$SE;REK*TmeT-E#d6-!%H=S=x48%<yLs zp~ARVn=?_P`-WU}oLTkz3H!IBQ(n?+Zlmy)A|BJYuRjq*Q5=dv^q!=FC<nR0aS^P# zfz*uj_>^;9^gtlWXc9Tt)oiT=Rph{=G6CvK!_${7Nm?TysWQDRZ;3|^PS=qQLWWA3 zBpYzP9YlzLi=X==@G`ihk=2x2Ak1uBpe;Ri5-y1x-Q;?4mnn6bK=zxcxHMURiDUDa zP%tr8?}Z$(ZMObSvj{?teqaED7m$DY0CWw8B7C9tfmOI(C{^enfd+l&8RVJQJ18`* zh8(lUW1^J>Ny&JWU3qu`T_@3ITvs%bT%*TJRCPI+7T-eSnfbt-#xg)s)_Y(Lv(!98 zKE5w3vIKMJm%LLUHK!fx+nM*QC}~8fF;y+N0OSR;c+nrgZIhWuf_SC7dIH&L0?BEu z?G?&N&2G}tvgj^9&OA|dS!;zwk<AoQtL0?Ho+IIV%CkrWU1TJ628}pbX{c>lktRjF z@D1!5%M`jyopt|E#JVvGzw$N#jP_6fq0YPvms!lK1+E6uR-5%`N;yuW>2*-U4ErgT zrI|C?viv{m^m@rY>ENI}rlUSU;l4-ws+HLzZE|fE<j9i#P@y>DhGY>SPu}BQZKPFM zkY*{4mX;R}Z-j)MMI#BdxiKmX^wprxM}{JsR;GoZC+UvXi1ToAX2D%I7e(lg;}?Tn z^ADg_aw>|49l6aMZZLha{s{MU7E0Y71i@V>0aiBY)Co_?w0Dk4)heftAK}E*!6e^e zq{1=&*@D!5C-GdQ(+YWtyG@Y3VMbC%&y*8fAnotu%b^AH3rd-KE99iY9<B{Z%B+UD zb4lG%cR<k4f(%<kXLRg*7+tI{WeBVZAmta{9ghUmyTH+xE!V>Q6fT1J&=a-SP+s8< zX5E2|%sV(BQF9FnNkqjwfzx84RoAJ`@ak{HwcQE8L#82DTA0sN+b^@%mn9XzF0)!q zYEX>COJvu^OJbD@q{_~3Q4A3}2Fox^K*)yfmfT;a)yb*8_|x`-@mqp5>H}M?<W?Jl z$9BWVo(d<^&HfD9#UDZR(G&9}EMh68+74=N3Gu}O`xkcp?<MWao{6+F=n_?Cs`W&w zskeX$vBj6*9fPA7-sa(H80}_oR@0U+BUZgDhQ)q3TXUpvymrIXag5Q!`sGN(Bga-g z&T}=rM^wItrP2|rW(3M%xNfPEee`N;5PIGL!c-by4ny;-yIq}G1LVkMT<R(+re3Pr zoU!e?O*_eVh!wF!{jo_hppjCeOHDgzDg)q|x7@C^JoI$)EWo&8I7LO3D1zN9*L99K zc)5POHCR<U<9sAf#8R|m-9~*wBYl3kP^z6)#!TVtuMj%isZ1EjY`bi@A#q1UWv6h1 zdcLio%Q!rm60g*=J<6}q_fgDe-1p0m|2VYtJb+pyM+X6si2N^|#J{GjJ)Kp@-DcN+ zJ=n9wG_{__e~4go526o~=$=)t=~7yYU#Uf;>c?jjN`w?Hjtdl%7rg~_Z<Bn7<(}kj z$Tf&WzI6X&f(ewhgG&1Zqh##WHPtl?a95Ll&lQVwyf4#?(66s1s<R{Hz)i%T0-wG> zp2$nrEXQL8;M@D=yZ1JN5nox@`_koLFRJb-(&H$S1J9F%o!ye}mDfh~0^EuMC;a7( z=FhY95H~l0)lDz>si*8pCZDQ9h<C@A3%TjB)$Oj!{Pi}*6OZd^4AIA33LFEkxgd4c zJPoh-&3i*<w<l!yPzftO1Ss9rO$H<qzaz0&-{;hv45W=N!kACGEVhTyc5O=^+^y0? zDm;Xd!rR<Sj$Jz)Z^I<1L4l^9@1^^n7;?TZ`-iQY=PC4$ql*HcN-cK^N5c(xmDrUA zR$qH5O<V2^fZh%Y8>75$Emyrj$gC$~4^2QzOsNw)3O}pQ3DhH!!}j20??b_%=D4#< z(QRp5`TNYZFW2#aSH-}7KtWZpk;a?nO4Z>d;oW7M?}|pt@~$nTcta`gr#r3G$I}2I z<mZlFFn(U)g55Ruu9Tjf$%Q4GDd$Zpob_31hFC?=!ua%cj5>(I68?h!iD}(^smiN% z{A<L(UW@T$XTkve1ihQ4SdJzzn@zL$X9bjASB0WXa@FF~Eu_<I#cB%!t(>!5y{o6Q ztK-T@I=~d)V?&()9>?z?nVo~6@Ai*yh6iI@YmX8u@)aC5T;8tP;Lk9#3OO~7pD=i3 zJFkb5$trFd`#vw#^#!&)3VP~A8jR$l9nVwGE8fsR3ZUstx%T>pO8NV;UZ&UkYn|xL zvrfI2cOv{}`6m%Dn%EC}H9A)P<)$j#chPQ;m1Z(@ypzM+W4eP+x9lcEEVkP0_o{6N zZapx1kQB|)#mD)N;jSYkhfA@q+Y~I6z)z1SBgVJG(+s&BtMjl8B)?kLUZ%Z;)9o!S z)*6(=9Lb;Ok`h=uGa{xXQumBs?lVJqtHPOY@H#g;)vS$$S?T7@5e7Yb6rzlQSus0y zjI|uwYS+Wv_D6%M`5P64+r4AzzIUzp)-{pwI-!L|>JJKwiC&)gAsqr`gwMN9mkXB^ zq#|u{oB7pq&zpXqlerA_2N3ZLd_OerO*70x72aY$nhN3z!ft+kXWg@&)X+OqX-aB% zITGvESy8`84#o6ZlIVQOx+asf&-#{n*Vejt|BBC$-q`?UBdfd;%vtri!+kF$M8)BL zd(ww0i`?nqPH+#Y^F+||GyeJ>gUN#vwl(5@{RxN9`=yHWiL~U30^TcQP=EHc$5>)u zpDypR=QQ@B-IXU&*D#gq!8SkloSI=LANV`4+iB1C^&pV9iNzB57IU5oDX5rhiLF<x z*m3kYWqx<}j;O_M=sb`9$=#4H*szVXs=$a$e{?WjJm+4@@<M9xFcS+5olcdiqEw{F za9!)wE!|B8S^9hV8?C9>VREmjalI^ChtQcDIz2Q)ZC_xfQjfbW1zk};)i1=-I>z?Z zH`;nPb^H{i;=^VSP8qRx+Wrv<Y&=Mo;V6gL3Aa1QGobL71$V>@FEJg~fQ91rIVt<^ zncg-lGCW=VYrW8pRjO}vH>}9H2ZumGIC&(m4h6n&Alt4u*(-Y|%RFIw@#irmjmRI+ z+IEGvyZ7@SuF(*~o6GICg#(}RZJ+%D@BK}FL+-Cc6?%S4JT!bIG#6^k{k@#y*piTL zt3Fe3Kul2*7NCLN$Ih`AgR)#3P?1jeZ)ZU1<t7L4@4-7N%+LDQyG`CRek#i+AVRAF zV^jm^#ru!>%Ti1i9EGbR4uzR@9`#d#C6I5f&M4z<fphzWB9TS6$tR85NOAvzuXhR( z9bnpZ$F^<Twr$(?8qch;ZQHhO+qP}vtnWK}@2kK5+f*vmNq4%ExABBYF(qKP0#Qiu z2F&G`Nxa|Nw&qG=w(mTjfZWUf5-xOG){7(XWmIE(I7?fnD?KihC>i8WDn*<zs2BZo zOQ!~SBswPm?WqPKC7^$5#8O)`Sbo7$J2+<onAX6-D*_9O*^+*b+xF_B;(ct_`SRNY zXYT6Oje#rPJ^e++8mQD5@iMLvk9oQW?BY6el6eD=1`-oh0F(S?qJw`*SDjkK393%B zw~uT^cFim`(f@hA(_n@z+`n1DZmGA%!+p>lb!gTdJs8<XbYtCa`k~d7CdcmsNmv;w zh!F-p{o%#H8J8j@y_DW0V&x@EJ5I_*m?j}K-pJxgwr3o-bpv;<imV(7eT1LR4)Rtt zjalK~D!+=n4Nyu!>U?>+*dpJ)nh%-@AqU;O-m7aT?(SCa#eyzV@saUy^D_sLt$XnK z(kkZiG5>nhQ)WrzN$5%VyOYtSRUXjx#urb@`-SI}XpStHU~nJl%z=7OI=fOj!ak=W z_@X7_mbDKwVe37!ktR-=<<a?~>GO;1ll$aPp4F7Cd9u&%e8_K|{OWluev$fmpPOi? zzLS}Z�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≻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 literal 0 HcmV?d00001 diff --git a/docs/docs/integrations/document_loaders/vsdx.ipynb b/docs/docs/integrations/document_loaders/vsdx.ipynb new file mode 100644 index 0000000000000..1a162961879e3 --- /dev/null +++ b/docs/docs/integrations/document_loaders/vsdx.ipynb @@ -0,0 +1,486 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Vsdx" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> A [visio file](https://fr.wikipedia.org/wiki/Microsoft_Visio) (with extension .vsdx) is associated with Microsoft Visio, a diagram creation software. It stores information about the structure, layout, and graphical elements of a diagram. This format facilitates the creation and sharing of visualizations in areas such as business, engineering, and computer science." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A Visio file can contain multiple pages. Some of them may serve as the background for others, and this can occur across multiple layers. This **loader** extracts the textual content from each page and its associated pages, enabling the extraction of all visible text from each page, similar to what an OCR algorithm would do." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING** : Only Visio files with the **.vsdx** extension are compatible with this loader. Files with extensions such as .vsd, ... are not compatible because they cannot be converted to compressed XML." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.document_loaders import VsdxLoader" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "loader = VsdxLoader(file_path=\"./example_data/fake.vsdx\")\n", + "documents = loader.load()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Display loaded documents**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "------ Page 0 ------\n", + "Title page : Summary\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Best Caption of the worl\n", + "This is an arrow\n", + "This is Earth\n", + "This is a bounded arrow\n", + "\n", + "------ Page 1 ------\n", + "Title page : Glossary\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "\n", + "------ Page 2 ------\n", + "Title page : blanket page\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "This file is a vsdx file\n", + "First text\n", + "Second text\n", + "Third text\n", + "\n", + "------ Page 3 ------\n", + "Title page : BLABLABLA\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Another RED arrow wow\n", + "Arrow with point but red\n", + "Green line\n", + "User\n", + "Captions\n", + "Red arrow magic !\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "This is a page with something...\n", + "\n", + "WAW I have learned something !\n", + "This is a page with something...\n", + "\n", + "WAW I have learned something !\n", + "\n", + "X2\n", + "\n", + "------ Page 4 ------\n", + "Title page : What a page !!\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "Another RED arrow wow\n", + "Arrow with point but red\n", + "Green line\n", + "User\n", + "Captions\n", + "Red arrow magic !\n", + "\n", + "------ Page 5 ------\n", + "Title page : next page after previous one\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Another RED arrow wow\n", + "Arrow with point but red\n", + "Green line\n", + "User\n", + "Captions\n", + "Red arrow magic !\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\n", + "\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0-\\u00a0incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n", + "\n", + "\n", + "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa\n", + "*\n", + "\n", + "\n", + "qui officia deserunt mollit anim id est laborum.\n", + "\n", + "------ Page 6 ------\n", + "Title page : Connector Page\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "\n", + "------ Page 7 ------\n", + "Title page : Useful ↔ Useless page\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "Title of this document : BLABLABLA\n", + "\n", + "------ Page 8 ------\n", + "Title page : Alone page\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Black cloud\n", + "Unidirectional traffic primary path\n", + "Unidirectional traffic backup path\n", + "Encapsulation\n", + "User\n", + "Captions\n", + "Bidirectional traffic\n", + "Alone, sad\n", + "Test of another page\n", + "This is a \\\"bannier\\\"\n", + "Tests of some exotics characters :\\u00a0\\u00e3\\u00e4\\u00e5\\u0101\\u0103 \\u00fc\\u2554\\u00a0 \\u00a0\\u00bc \\u00c7 \\u25d8\\u25cb\\u2642\\u266b\\u2640\\u00ee\\u2665\n", + "This is ethernet\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "This is an empty case\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\n", + "\\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0 \\u00a0-\\u00a0 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n", + "\n", + "\n", + " voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa \n", + "*\n", + "\n", + "\n", + "qui officia deserunt mollit anim id est laborum.\n", + "\n", + "------ Page 9 ------\n", + "Title page : BG\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Best Caption of the worl\n", + "This is an arrow\n", + "This is Earth\n", + "This is a bounded arrow\n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "\n", + "------ Page 10 ------\n", + "Title page : BG + caption1\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Another RED arrow wow\n", + "Arrow with point but red\n", + "Green line\n", + "User\n", + "Captions\n", + "Red arrow magic !\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "Useful\\u2194 Useless page\\u00a0\n", + "\n", + "Tests of some exotics characters :\\u00a0\\u00e3\\u00e4\\u00e5\\u0101\\u0103 \\u00fc\\u2554\\u00a0\\u00a0\\u00bc \\u00c7 \\u25d8\\u25cb\\u2642\\u266b\\u2640\\u00ee\\u2665\n", + "\n", + "------ Page 11 ------\n", + "Title page : BG+\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "\n", + "------ Page 12 ------\n", + "Title page : BG WITH CONTENT\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "\n", + "\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "\n", + "\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "This is a page with a lot of text\n", + "\n", + "------ Page 13 ------\n", + "Title page : 2nd caption with ____________________________________________________________________ content\n", + "Source : ./example_data/fake.vsdx\n", + "\n", + "==> CONTENT <== \n", + "Created by\n", + "Created the\n", + "Modified by\n", + "Modified the\n", + "Version\n", + "Title\n", + "Florian MOREL\n", + "2024-01-14\n", + "FLORIAN Morel\n", + "Today\n", + "0.0.0.0.0.1\n", + "This is a title\n", + "Another RED arrow wow\n", + "Arrow with point but red\n", + "Green line\n", + "User\n", + "Captions\n", + "Red arrow magic !\n", + "Something white\n", + "Something Red\n", + "This a a completly useless diagramm, cool !!\n", + "\n", + "But this is for example !\n", + "This diagramm is a base of many pages in this file. But it is editable in file \\\"BG WITH CONTENT\\\"\n", + "Only connectors on this page. This is the CoNNeCtor page\n" + ] + } + ], + "source": [ + "for i, doc in enumerate(documents):\n", + " print(f\"\\n------ Page {doc.metadata['page']} ------\")\n", + " print(f\"Title page : {doc.metadata['page_name']}\")\n", + " print(f\"Source : {doc.metadata['source']}\")\n", + " print(\"\\n==> CONTENT <== \")\n", + " print(doc.page_content)" + ] + } + ], + "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.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/libs/community/langchain_community/document_loaders/__init__.py b/libs/community/langchain_community/document_loaders/__init__.py index 5952c4a08b37e..d4c5436525e94 100644 --- a/libs/community/langchain_community/document_loaders/__init__.py +++ b/libs/community/langchain_community/document_loaders/__init__.py @@ -207,6 +207,7 @@ from langchain_community.document_loaders.url import UnstructuredURLLoader from langchain_community.document_loaders.url_playwright import PlaywrightURLLoader from langchain_community.document_loaders.url_selenium import SeleniumURLLoader +from langchain_community.document_loaders.vsdx import VsdxLoader from langchain_community.document_loaders.weather import WeatherDataLoader from langchain_community.document_loaders.web_base import WebBaseLoader from langchain_community.document_loaders.whatsapp_chat import WhatsAppChatLoader @@ -394,6 +395,7 @@ "UnstructuredURLLoader", "UnstructuredWordDocumentLoader", "UnstructuredXMLLoader", + "VsdxLoader", "WeatherDataLoader", "WebBaseLoader", "WhatsAppChatLoader", diff --git a/libs/community/langchain_community/document_loaders/parsers/__init__.py b/libs/community/langchain_community/document_loaders/parsers/__init__.py index 9d01c3df2f5b7..8b635e9a68bd0 100644 --- a/libs/community/langchain_community/document_loaders/parsers/__init__.py +++ b/libs/community/langchain_community/document_loaders/parsers/__init__.py @@ -13,6 +13,7 @@ PyPDFium2Parser, PyPDFParser, ) +from langchain_community.document_loaders.parsers.vsdx import VsdxParser __all__ = [ "AzureAIDocumentIntelligenceParser", @@ -26,4 +27,5 @@ "PyMuPDFParser", "PyPDFium2Parser", "PyPDFParser", + "VsdxParser", ] diff --git a/libs/community/langchain_community/document_loaders/parsers/vsdx.py b/libs/community/langchain_community/document_loaders/parsers/vsdx.py new file mode 100644 index 0000000000000..109521e48cc9f --- /dev/null +++ b/libs/community/langchain_community/document_loaders/parsers/vsdx.py @@ -0,0 +1,205 @@ +import json +import re +import zipfile +from abc import ABC +from pathlib import Path +from typing import Iterator, List, Set, Tuple + +from langchain_community.docstore.document import Document +from langchain_community.document_loaders.base import BaseBlobParser +from langchain_community.document_loaders.blob_loaders import Blob + + +class VsdxParser(BaseBlobParser, ABC): + def parse(self, blob: Blob) -> Iterator[Document]: + """Parse a vsdx file.""" + return self.lazy_parse(blob) + + def lazy_parse(self, blob: Blob) -> Iterator[Document]: + """Retrieve the contents of pages from a .vsdx file + and insert them into documents, one document per page.""" + + with blob.as_bytes_io() as pdf_file_obj: + with zipfile.ZipFile(pdf_file_obj, "r") as zfile: + pages = self.get_pages_content(zfile, blob.source) + + yield from [ + Document( + page_content=page_content, + metadata={ + "source": blob.source, + "page": page_number, + "page_name": page_name, + }, + ) + for page_number, page_name, page_content in pages + ] + + def get_pages_content( + self, zfile: zipfile.ZipFile, source: str + ) -> List[Tuple[int, str, str]]: + """Get the content of the pages of a vsdx file. + + Attributes: + zfile (zipfile.ZipFile): The vsdx file under zip format. + source (str): The path of the vsdx file. + + Returns: + list[tuple[int, str, str]]: A list of tuples containing the page number, + the name of the page and the content of the page + for each page of the vsdx file. + """ + + try: + import xmltodict + except ImportError: + raise ImportError( + "The xmltodict library is required to parse vsdx files. " + "Please install it with `pip install xmltodict`." + ) + + if "visio/pages/pages.xml" not in zfile.namelist(): + print("WARNING - No pages.xml file found in {}".format(source)) + return + if "visio/pages/_rels/pages.xml.rels" not in zfile.namelist(): + print("WARNING - No pages.xml.rels file found in {}".format(source)) + return + if "docProps/app.xml" not in zfile.namelist(): + print("WARNING - No app.xml file found in {}".format(source)) + return + + pagesxml_content: dict = xmltodict.parse(zfile.read("visio/pages/pages.xml")) + appxml_content: dict = xmltodict.parse(zfile.read("docProps/app.xml")) + pagesxmlrels_content: dict = xmltodict.parse( + zfile.read("visio/pages/_rels/pages.xml.rels") + ) + + if isinstance(pagesxml_content["Pages"]["Page"], list): + disordered_names: List[str] = [ + rel["@Name"].strip() for rel in pagesxml_content["Pages"]["Page"] + ] + else: + disordered_names: List[str] = [ + pagesxml_content["Pages"]["Page"]["@Name"].strip() + ] + if isinstance(pagesxmlrels_content["Relationships"]["Relationship"], list): + disordered_paths: List[str] = [ + "visio/pages/" + rel["@Target"] + for rel in pagesxmlrels_content["Relationships"]["Relationship"] + ] + else: + disordered_paths: List[str] = [ + "visio/pages/" + + pagesxmlrels_content["Relationships"]["Relationship"]["@Target"] + ] + ordered_names: List[str] = appxml_content["Properties"]["TitlesOfParts"][ + "vt:vector" + ]["vt:lpstr"][: len(disordered_names)] + ordered_names = [name.strip() for name in ordered_names] + ordered_paths = [ + disordered_paths[disordered_names.index(name.strip())] + for name in ordered_names + ] + + # Pages out of order and without content of their relationships + disordered_pages = [] + for path in ordered_paths: + content = zfile.read(path) + string_content = json.dumps(xmltodict.parse(content)) + + samples = re.findall( + r'"#text"\s*:\s*"([^\\"]*(?:\\.[^\\"]*)*)"', string_content + ) + if len(samples) > 0: + page_content = "\n".join(samples) + map_symboles = { + "\\n": "\n", + "\\t": "\t", + "\\u2013": "-", + "\\u2019": "'", + "\\u00e9r": "é", + "\\u00f4me": "ô", + } + for key, value in map_symboles.items(): + page_content = page_content.replace(key, value) + + disordered_pages.append({"page": path, "page_content": page_content}) + + # Direct relationships of each page in a dict format + pagexml_rels = [ + { + "path": page_path, + "content": xmltodict.parse( + zfile.read(f"visio/pages/_rels/{Path(page_path).stem}.xml.rels") + ), + } + for page_path in ordered_paths + if f"visio/pages/_rels/{Path(page_path).stem}.xml.rels" in zfile.namelist() + ] + + # Pages in order and with content of their relationships (direct and indirect) + ordered_pages: List[Tuple[int, str, str]] = [] + for page_number, (path, page_name) in enumerate( + zip(ordered_paths, ordered_names) + ): + relationships = self.get_relationships( + path, zfile, ordered_paths, pagexml_rels + ) + page_content = "\n".join( + [ + page_["page_content"] + for page_ in disordered_pages + if page_["page"] in relationships + ] + + [ + page_["page_content"] + for page_ in disordered_pages + if page_["page"] == path + ] + ) + ordered_pages.append((page_number, page_name, page_content)) + + return ordered_pages + + def get_relationships( + self, + page: str, + zfile: zipfile.ZipFile, + filelist: List[str], + pagexml_rels: List[dict], + ) -> Set[str]: + """Get the relationships of a page and the relationships of its relationships, + etc... recursively. + Pages are based on other pages (ex: background page), + so we need to get all the relationships to get all the content of a single page. + """ + + name_path = Path(page).name + parent_path = Path(page).parent + rels_path = parent_path / f"_rels/{name_path}.rels" + + if str(rels_path) not in zfile.namelist(): + return set() + + pagexml_rels_content = next( + page_["content"] for page_ in pagexml_rels if page_["path"] == page + ) + + if isinstance(pagexml_rels_content["Relationships"]["Relationship"], list): + targets = [ + rel["@Target"] + for rel in pagexml_rels_content["Relationships"]["Relationship"] + ] + else: + targets = [pagexml_rels_content["Relationships"]["Relationship"]["@Target"]] + + relationships = set( + [str(parent_path / target) for target in targets] + ).intersection(filelist) + + for rel in relationships: + relationships = relationships | self.get_relationships( + rel, zfile, filelist, pagexml_rels + ) + + return relationships diff --git a/libs/community/langchain_community/document_loaders/vsdx.py b/libs/community/langchain_community/document_loaders/vsdx.py new file mode 100644 index 0000000000000..e0929e4019279 --- /dev/null +++ b/libs/community/langchain_community/document_loaders/vsdx.py @@ -0,0 +1,53 @@ +import os +import tempfile +from abc import ABC +from typing import List +from urllib.parse import urlparse + +import requests + +from langchain_community.docstore.document import Document +from langchain_community.document_loaders.base import BaseLoader +from langchain_community.document_loaders.blob_loaders import Blob +from langchain_community.document_loaders.parsers import VsdxParser + + +class VsdxLoader(BaseLoader, ABC): + def __init__(self, file_path: str): + """Initialize with file path.""" + self.file_path = file_path + if "~" in self.file_path: + self.file_path = os.path.expanduser(self.file_path) + + # If the file is a web path, download it to a temporary file, and use that + if not os.path.isfile(self.file_path) and self._is_valid_url(self.file_path): + r = requests.get(self.file_path) + + if r.status_code != 200: + raise ValueError( + "Check the url of your file; returned status code %s" + % r.status_code + ) + + self.web_path = self.file_path + self.temp_file = tempfile.NamedTemporaryFile() + self.temp_file.write(r.content) + self.file_path = self.temp_file.name + elif not os.path.isfile(self.file_path): + raise ValueError("File path %s is not a valid file or url" % self.file_path) + + self.parser = VsdxParser() + + def __del__(self) -> None: + if hasattr(self, "temp_file"): + self.temp_file.close() + + @staticmethod + def _is_valid_url(url: str) -> bool: + """Check if the url is valid.""" + parsed = urlparse(url) + return bool(parsed.netloc) and bool(parsed.scheme) + + def load(self) -> List[Document]: + blob = Blob.from_path(self.file_path) + return list(self.parser.parse(blob)) diff --git a/libs/community/tests/examples/fake.vsdx b/libs/community/tests/examples/fake.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..4e6502942eaba0b96dcac5fb4084e528c7f6f6cf GIT binary patch literal 337190 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%0Dq=fIYOQ??ZKgnb8j zMk64yy=N(ZeDoB@`f_NoAhEn~UMO&UBm$?i;|{AxYOX~aYeT04ZAj76d4}aYr6v0V z3sN&$mG$jR6qTxjmFh527+cJ|sG94QYKB_$%dk!uRS%ocP#nmzUA66!f&GK4FBr`+ zHc^<SsTn0z$28iKy&(r0^mBj=T`L)?RZ<PpTvg=&8V;c0PYxR7r!t>oh`}dArXf&# zu8AIvR4El@F@M6z{1H{uO^!@;6VAj$LG*+wbEmv9^%|`4ISq>9!}VdJpn;t~f)28q z(x?PEApwKEB6#tD2bCpMX=nzOjQtj71^DX301C=gR4OEJJH*W;fD<Gk9%_YTF+97( zDIzN3Ta?fci{X=7;?`Iih>oBs7R!ZnF&M|KpcI<Xuq$qbe?^?phpLMdQ*s0J5?{<8 zi%augc^3UpzEVn3Jq8WvYTyIdhA>0XjQnT7R2K#V>Z&@saPR~MM<f(39l1Qj<tR~} zZwmkiP(csJs0D~bvsnWKYZ4unr{1VwEofL9EE;GaT8xAd?lQoVA?%g+M6l=$Y~RKp zk@e~*VHh?lD!e*$dz)~DW#4eizF8{zmIi}_rB#Yf4Uu$Q)m2MQ%`mi=^y<*gONNH) zF)YNaRt@3roV-`&eOxi)p;atXHEmUcAtSr=HJ;B%d|b2B87bV@Zo)%M#5CUgcTH|) zX@+An+ir6j8RPt6PJ{k?Xdxkigm>TVf@JwIwa5ADC)*x^M3sZMy}M4#A?#m0U-LV- z6*LZioAexLWLdL|i=PzQRSer$27|)nFJb56WYXxU8-f%|VT$ukp)eQ3r57jp!mP*W zw;AYjONOx$#n3&Pc`&va=smYov+Sxvk?{~yrQ&+n62hqzJva(zhJjEkmi2No59M>i zs+4TYs8}!wSSl7TJ!M2NVMM6dwr)akORZR|TqDv^{TmJYrSpTIgA90nY4CQf;UL~R zECW)g`&T=Y@{M`+^(qHHhvkRhT1sk>&OD7}%X(awZ1&f2Ci=!}eqt|=i|Ez=edOm5 z)5HtEu==MKb20)%67YS^%n^+(_FPI-_H*DI&wYq9$uvPaL?1UfdrYZ`X@U#^H1{D) z9M=&qs4ycCojyVWabk3~B4dV-AYR~!%CfmW)rEZ;FY#<dh$M*GbmTfTtHHMYCL0P^ z0&3uzqnH2Gi~-{S-3m5Ci1x710bJZW4pEvyW<o;=&LNZOf{%~Pt32jr9#}SOAhFUZ zx0a(JE6-X`Y5YSGCXi+R9Vsj>>qkiw56V3qcmq{_#pk^W&}5gq9$j{Q6h;MJH{x-l zWE_Av@@$QEnI+0nUPL{C7+5IyAm%c-1rZL;V0}mf05bAa$)&YMg!SQfVIxmgnndoA z03~e-FVdPl>EtjetY>~EoMG7&jhBh=%GoEDiD<5_d3ISZYL&8AG)kIZbkwR|H2tbu zf%j^~^!=B^8RoeSuSnI>HNB$i2E~?n3TGJSrmk98X=_*1vH_3E6;%#6<1krqn5>{D z>@F!DK33KdWAAmQOwcf{O4NkLBY9E~jo{$LC#H&Ae2l0W&R8;dc(Nkqjpz|C5CNZE zvj`XGgb`@uCFCHesCo6M6ICDw;0)p>UQAwedF&QAgAoz&VOQ}-OelypuQ4&gQfV}g zPzK44`~t{4z-<a{_$OXH)`boUH=1MQLJ~+e;~LVA`B>kWDsF|(66rdyt2uFlH%e5( z=!2(GVrn8OA@GU78F{oIkPHpBxs>x~$dw|y@rSa^{uG79WpPF_sxQdPfdg_t(Jn2i z@FPD*NR6g3Xa)o8cnN$Esz3-3p3?|$9(i0cuJEbQVIzr{>EJ01S~d*9sELC`2W7!a zSNMS3YQxW3oUtM9EVglu_gMOSa94019tL3xKqG6M!1#wDjrL-K8Dl-$GhqwcEMe^h zHge|$8&6;hU^`bc;4|s@z789PZU9@@c2W1LhK6YWSjh2$*g|+umdaRuqUx$@!@uNd zY*DV5D&SSAq8Sx!RgD9*I6#X7w4f(xKBWeWv;@Y2YszIu3=F!v7%XT=O+}I`9~lY6 zV9nA|NUnfz28;%4!R^AKlIY}Ohz}E8c9EofTHgh<fP*1B6Q&Yesso42_mQI{O8`w_ zCrMcXalS`=a;c>H!h{ifU66|+fdp=k00+5*vaU4j6P2I^B$TLr1O<>6_0bx%i>Tno zRggUNK}vhP5)zWy1P<6N0nQi-B{bZM6;wRQvz*c0jNpx^9p7QOZJ01bZ__c=DW#<b zlN{7wnK>H$P(aU9N!lu!+{()HEVSSNUIq>92Ehsr@dZ4<4v$g9Fv0|N#uAJU7(>)a zG<ys~+*3<67+;X_LadMFt+qK*8Mgv>AvOSoHwHlqR)(U&N@*Rm(Abu{I=Yye`a!@g zu`tC_`N0NUv5qGLDlmE8D~w08abeaEqG0zpqkyd_AX+f$ObaOQO<JE3gB5Y?5zckm zN34u#;eYie0eHhvhqud{l9^;ALc9L&{X=GC=Ug=G>!^7K#SYW9v4t2lA6^)$Su9r( zaLlNcu?=^%;uO_#sZzrd&6=g{vbNL$QaJhfr)0vr@H!j??`EF|KyFa977hk!?tSoS zmgIiUjG=!IJG1_V1(;WxmY1$xvze{30<%0FEkCW4dSE<Vi#tPFh6=YAy@HiQRt3JO z6!eTiSXR9yFOAaqF4YcR8-paYF7H8JYqi;K`8WD^hD%25pcD1;rWhJ~&(OqeEXTB0 z{WI}6I%`cP<Apf(gH}fb6>HDp@9`u)qc~$NEHG<_tr?x;(Q!W?wfX#h9FFLscHLje z6OSL*bEO{nQFaqwe{t_8t<jX9En@Vz886_Y6LsEq@2x|_>{ld42VYS?R~6zID&)wH ztLG_{UrB9BR7pEuN$Ci`AwF1gky4WufGc8Z)}(?(u#p}T*1%~*jm;fVp;#c5mB3Rw zXoy)Pvq;o-Mgqg2L@7FftQ8QBR<BT!E|#)Va1hFhBv#YLxsBgBaT|HJD0~iaxxUTb zlqg5mC^6k;(CI1H!A46x>Q3rShL^`j(UN1LL_3P(OiaZ^)-pg|x<3?kFOo>>r^TWo zI+2oeXC>-dRJtMNEf8yL@T!d`i`JyG-qf{{A%jQvUhP9OL?a4y+3+t;{ztQFuBKux zY|GU?o?%xly;3V`2HbcNz{4q84lpDfbu``aT-DONmzzK<ookwnSZ_&U=c#x!CNw66 zodt8r(xsG%QJVt3U&VlNu0D<T(n<%E_#>|%&iN7H74NUX=f!x^5#ZwQ2t3rCz-E2+ zOXF;gJ<`sw#yFUCTWxGwG#Ssvv(FE~*$^wO=MQIW_-+em7XsT&&${C2aP~{}*E8(r z5%xxBmk=}^cV~EN1QBwaguOna#aTF-2ZZCkb7$i%MxIS9`;JO0hGDpvELMuc6x0e8 z^i@={=On!T)$tLy+K+D@uVn7jLL_kuo|u;{EUDEfs*%{4B8ge<$Uc=yCq?{(N7h2{ zV69S8hm}rs<yzuGk_kBGjr&cyu6A_A)OU$Ik}T?{M<2Zq*H5V{EnRPfn^DRo&io~r zL|>MfuZR4~JG+8!OA?FQ2w%iPVA}Li$pROizaz^UV;Gl1q#YkglXp#;y5ktOJeemr zcv%tGAPmF%KduKdJHnR@5ok3%fiJH^vU`0VwT6Tv)}xqBd>Js~1%(00oWim;o`&>l zI|>RdY=|_(gAr{E;eRByIKoy**kE!z8R9G6Rx*UYbN~T6Ski!k$l3arhYrHyvVlqh zMZ6iLkG+ckim=x=9jvNBXXO%N?&~F!mmRsSsQ^(mcp#M=!z@;H$1kd?k0sDmqsIKG zVArrS<PM<7gcr}ZHKfUJ`8gDniA9#;mjv`279zc&y}fz?1xE4bn54#7Eq2o(^XtWl z`}XZ?xqttdPRF|%i0EHezN&+5h;6xtm0Z$G@cyT(N2R^x9mMV3b!!}Y_Llb?w_@3P zwk0%MVtWqgx73KUW+fK@6uqRa+**?2ppuglx#SGS^9`1v6~!4Sg}snwfJX7=+}N0# z!IHd7H*Erd_e#t|h#3A;?5o=jyCK5(V^8fu*#7c=@x=}8fT%DU%7`j!W`Z}fRq^yP zAdy85RZk%*M0fKM<;^Xc8p78Z*nk2-=zO(UGd#_P*IK!(?t-YaZ;Pj_bCZYu#v6Hv z%mFJb2Pb%t<yir+@>V5jLMG*2Z%&ffd4x)48&@^IG`F~kNuEi}7(8Sts~1om)_><F z8{dpaJedej1hyuk-?5)Ix=H>4EzcHW(!2#*(piK5CC}c>fkUaru4qowC$ED!X29@# zkemH+pxtl?j|+MTEnnYZ=5KkNN+|!HVKHfL$CI_%C3Amzx@iz?y%c{U(;1qJDfBIg zt4s96bR<bgXf!_=^JcY2@PAPv@q#0goaJR1Sw3O_Z^|cWmlF3s`wqlyG@U(xLxBwI zWDbM>T8|8S>=Mru%<L^_k`;6uc>By#o~mUF+!JC&@oFf$cIIz==wK|xPte_K&o5k! zN8!e$bj(1THi0{9UE2iprl<h8_kb}%+NbI9+4pHuv2vdm;Q3nsyQ)#Hl&WMZLv%od z7{-!++lQ&l^{WQ(o~~6&?hdB1Zw&0VaUaXUyltwaI4&`(Js0ZR%~>n~i@Q}jAj5Cx zW47O}m3T0m#ZQT|Nvr+a8Sk<b??-v|<@bNg!v{8-pZ$6UBl?%$Ct)x+YY)cZbj+5$ z!)#$RtkSTYLIpwOMcp)RWRjppJ6I+qw2KdKW0O$C3Al6ha}1c;S)veu_p4mqHEDUE zB<7`|i~vT)`A96<A|uNF639I~sh=4bLEcJ-j08z(F&`%p#OaEwST7-?L9E@566iWs zrRP9U%lE;cTV@cBIhB^)A*ulXf-Xrhv0yKkhjtuS;4N{89W8SlJCys_>yzZLpOth+ z)<T~3@rx1rCV@OnFN<Md6p8Q<CRTD~e@J{&#rD+%ZMNB!yXv}FEm*-^q3s}Ip=TRK zyXMhUV69@f*s|Mq%>%w!+P!H59Lid?b^|I0d~-tBbG4C<QR%ZsIoc23G=m9X$_R)B zPF3NEx3Go76yBTgT7)gRahPIbTlfh`JKCPz%_l)?F2S96Gw%kuUyTmCP>R9Uk4+ik zJHkHfOhS);fgbn_=K*%L4jbYaPsF&&+xpvj;D`0&qrQI629St9`JyFdsf)8~^r!^; zEs9ZGumvIl4)4v)l-yv8aT24rCzzunc3NaC(w6s?thb>(80zxSWy91XB)Gg|6SYmL z16Ognl8(rVqrO@~rhbep_LmT~C^Vpp-9d+ieAj4qOS*=BVwMtyD61LrMvPHsMPI(b zyD`c{^p~}W?`K?qt3?vI+^vXN_WfpYLVnZ<_~J!{Vxvep<&7bu?IUsLPw_*vnmys? z&Z>$%CJER<T#m9!o*@#qPe1ye&y%2RBT99{*;?GQJ_Iwyu>|+L4<<uAx^f$B%jjn{ z#~%{|RW#nOa+|%l4NUgwHKPpVL%|$v4|{Q2*u~q@9qcPrb9Kvm!C;Qt7;FwUo_7Zu z&r92Q;-|wI9LPTs>=}%E<O`V?d8VJIvoHUGm-%e`FvhCmhkl6j2^;{XV(s83S}u6l zew)WE8h{weWDP4QV^AWtW@gaB1FO^Q8J^Q?yscx1H3G4|2yjT$?i<HP_r_`So;Rb$ zo3{~wuWtzMyn%?@&mPcbh7gUm9yYFiDy4H>f|JL`I%-NEY>(*>;3AkHCyN0WK^-|U zz^dm4=Y>>10X_f?`$<Teqxy8QJL`~<kf|SOx9KY6zQN|Cd<`r65i^CyM>5b_HVr;? ze{$t~XQjSn>`^Bsv8Zqr1_Jvj{}i#fjmy)$C?GCJ`KrA5-6ee+ovZ~u>tj(PXkh|; zn!dB%;^Vw&W()s#aL2?3ldjJuAI3aScz>5X2Z1EZ74i|sloldLVm8b1iirJW)=HJK z@7OOG_vrw4wu-iJfIBY@cODXx&uBiR=_-Och<gyi&@-CB_;VKyg0oII6)Qx#SW`!y zjcf#(o=pPKN7L3Fcre*D`11Qhe>@5#^AQ<}9>(pyOj}vp4hDlc#7u{+$&96tx73e2 zT_${0=&d?QJPsIq6m%XZJm3Zd?`ha55qOCz&@`8#doVmG5gG!qaatGSYX|0`SgZ#x znt)IW%toC>I{vnsJzj{7_7?4!x$ZoM4z&o(!NVmLxQlz3Erfw8-L$lgHB;;WLfn!B z8g(HLR9ON^6jVt9x5HG_7lR_2i!c$<^Ad!Nyadq$;1@~MXM6&hbQPonrGyjwDI$`n z5-N+Sq@A0ZZWzmm=Guwr$BPueSDo=8=jCf)>j_bhj~en-#0I6V)Q_pOo^%b#FF;A_ z_pog84NOub#i_>{J5Jb$FqILqCR-o)R?vN9V-Ze(N;$GN?=TLG1W7yO=~is40plXd zg&~UH132<F2d}!MC=BkST%T`6BoVkSGCr<FPwRtjs}>xJBmeJT9tt<%-5n<B3yrBa zpJEMMRFce^-OTS=qryi#FtHl0(8oHtEN>oCO@hQ9TyhdZ9k@!;i$3!^11vPAmM%`P zzr|H+*6zEXMSi1ew~AnrCEKJ30@$AGY1eMkD3!~4)hcQw9s7lvnqG9UH79oWsvvl# zgK*s53(k-oT)P+WQRcu&t7nZ4uHE$CB6;2KH^DUxhY!FQ_&*+nU;Z`OX8nGhhhl$B zZ}v6(IUXJOJ>R*eGpFjf{7pn@T!?7txF!hnSQn3WdLd?6{0*}QN_q{HUHM{3H9h(` z2o0XV3lUyL!J{5$7&wJrNHU<rt?l}qjJms4T+RP-;<zUzrFZ~tx`u3oAOj8cSQmgs zPCa_lZ&+-SO6UgdLg_0448>G<vo5F+t}hvAqSkW~ggRLr)(^!m*LOnC&By1^PC$&f zZ+H48OHHp!D)WtocKomKmMFp_=L4Pce{b|ZgnJ|`jZZfu-C2owEyP$KZ(Fr03B(Xf zyL3YkE>3t|$vNel(O%+MMJ9oU&5OwPUn52fsMAnHyz0JMBD;&W&8mA#v*1h&%uzF3 z014Ns6>ZfpidA2$xK%{2wEY)cBc`6`eOhQWST@vSGaHa-n<`tl5-+!tR$9M~Y`GR` z$3P&8f3!;!%5UI!!~XPf=%(*|$yo+5?CW?#OUB-e3pH3h=7W}uL89(?)>xMN;T(Rn zBr{e4Ko5c-Q8W*P!JGwWG9etn(lZ#aLFWgsxJZxdeNI<D`1g^-bq?b8?z-<`iTwT- zm#@h`l&=Q!gm^eT2Uv?`r{8N<-0SOOgsT=}FGqG2Y;!j11f9YS0&=$~RCvL~YZ+F< z!{D;c1VbGB;)bY12tE{7{PIt%vBiU|K+$~>?0fqE{+Sc<Z??^p;CTa29GGRc@!auj zPlGkbD0-%aElg~uR<tw|h|F@;s^<D7!+1f@or7)WV4IN)FI#|~{V>bHHj_SC#o52- zDV}e2+x+(0@ib3n#Dfhb|L)HtAm|xB&R|$vmVil|yaX`oN&O7+!lX&oGQ)D)u=!-| zKtjT*4%q%;SJhskeFx%J^e*?>76*2=VcQIwUvRPLYTQIRh*>MP&0MB=!MufNuw?FR zQe~ZOMqeKqTD8*EQYi@W@G>->&o-fu*I$0`VcQYfd!!)3{NOx6BQJ|RO0I2It77N0 zZB~ZcZdFyaQWfT$DppR}j!otqr|1H7s2<`@yI%F>R!$wT#{ql%8?gucSNI<rel5|f z#TFKk*RU&NI6F)bQ8zr9o*wxy0k)j#Yo<+Bij${&#CbpRUP*=vK3RqkJhr4DLShGm z_<9hsyM&AexY)0h5J$*izmPbF4zBlGq?NZ~f59wnI}pOVNMJi#B(A{!kYf6W+GofC zaSTH031z#y04dUK5T`qWko5tc@!p?UL~5A*Spa@u&x4psL2xHQu^Y^@Mv?988RrNm zNDh6ILhEDDCa;w{KpR@UIT?quC|u1HetTk1G%~(mD(Ybpe)%2VeA6g~O}87ue{WJ4 z&qXZg357AlR%@ftm*0cwH=T!yqCXp3IJCH0xuzLax2PKkQ)n19%tKYmM$y%LY>8nS z701+G4!CSHsdSi!g3D(Cah~I8CfOS}6CgWgp!uzXe@xP0n<k$2v%$m<n`G(;F-j-l zLqB#5&Stj&NA$G)BZH%*2m5Y>)x*GUG9>PUIH@ysJpKRdUE6ZvND_VTi2esXt#<=x zT|rXpxFd7{L&Wwpz1=Yr({C7qj29a(m%;A+q4|&bu|1hpx?&h%yM3W0M+hlZRVtNm z<smEc<c~LYhq7F>ouFBFrSmO(INC_|N@NlQjuMyLf)L>4@_KoEl=q{_INwUhGtT+h zT&xyxN((F`wJc@H!JJA)K3Lsgh3OJ85D1D`D3ahpLGkI|29W-9FcIv|%9M&kz(*GY zusSc^DfjmGy~9mM!lN>4N@mAz`dSl#;4M8wnpt@My2yvoaC|uG=pC(Th7~+YQkH%k zj~AomD{|AR;1Qr;F30SH+;r}Tmg3o`LQCaz^^d2b%Z*E!Qq|a%8Kt|+@Yo#+TG~N) z>>`3lI@f$A@``!hW&{wG04Kh_Dik<uqf)6TDc1;0&-NR7ixOa%Z4FUUx&uZ*WXAAp zx1$AmWmF=Y&8u{7u29npKuvP$sX)`+%Bm=>6n}9<%AZ$bSAMLDB@YTT?VwC{QwAM) zB!jT@oh~KEM=V$g$zR%*Uf*IHvOvH?xr~=k6BNop2QJB$t=aGrYBrHSq7#67C)EAG zGfZ96o)Z@}ZOhS+z07TCEzm}1gLF1l*>;poLCw~T5t{*9X{E}3^gP+;A0^D|Dcp?Y zd~#1pL1}faR^2MKUR2iQ&#SR3KUT$(2L*|CuqnGTL>&gBFOwyC4NZ}STLClIbQi7i z?xH#45*#`rWjRv@XJ^VlS>9eFz4ugEW?Fz>wYVWNYk{LoS-?4D@tA9ABIF%wV}H$^ zV_8ib@mR#Q16xohflO30-kuNr*zZ8$qv<KE;RL8VL*C1<6Q;0c3l<W|So8NuD{aF< zch#yw-d|X9`)627<B6uu6Ae)SPN#L6BkGtS>T-Ih^F)9Ec1#3ypJt>*9pRueB%~Of zfuV2!F6PTc{cJW|QKWkk(7tchR!_7E66Zzscf_IOYu)vDU*-cmwDQaG5u6V;Xb;qX z&(>gMl;ip8xfzSveb4g>NbNabnc~&hWd>u<!-E2$+nKFFQ<yAf@?@`k216dOsD}{$ z_4b9$&h*be;H?h4NK9|UK1(_!Up@y#xB80R8*E~cHOsGLSYcaln*fjCTqL+ijMc=n z)Y4#6Gqt8qj#&O+G3tfKNB?V@O~co1yAk*%>}{rLH{77zY1nQH3zMPOc06kju<7Wo zef0h(hFIsL(Ng)><ZASJc{?46Nq#b&re|M9H_PPg{ZBCs5LNzla<)8Y)lkLcRWW%_ zVJy=_U_GX59E<<y`ZYK{mi#{*OTeUhh1iwRTx8zB5fXN=kdttL$9guLFcKu`#q4TG z$;;~TK?zgZ$)}g?z)rn;!1g_YwjR)(Le&TMF3lb<c#tW_1i1liYFtE&-4R*0Nm78C z>!f}T#c*(0q5DDMeD~=CAGX-|D`>7<bl>LNiY3hae1&&M9IH$^P^5q4bH0tsm9mNI z;#ThX%)Qy<`c{g8?F`d~mYUMM`4DVJq<)ofwgO{*Stvsc(U|o_ox%LSD}>$c1~g0f zpGVWl0x67?JH6kt1*Rwry^qy}%LzyQNrr0*|HFI>fIy*u(R_lKKNMxcgN5>U^dAM6 z$^?8%2r*RP?G6`tOvg0y{^R~pfd%xW8-B+_rh1s(^b+XFbitzp?t+G9Sa9I7+sJ>` zHaxIiyRPROb~_CAaeJ@OQvw2Ej!AG2<3M^*$49-NvqqA*P&Sf}*}!5b85gJ``EsF; zj~~e==ZSiSl!O+4u>0@J(5FeRiA3EC$;;?n(y78h)w{xg>~YFtR}LxtPEh5Pc9#Tn zzRj_7MszN44`HCUZ)_5{_)}{j%%Yt-uAzW)2QH?fJGVWN(srV3L&M6DbY5>G0}yNt z+ejdhp%Kjexyw&#(P;xpI4#E^Oj5?FJT&1r-gJnmz|$M>BR3kJ<%8|9)3TZf%ClSc zJ{BFLGUw!20|-h~fFuoNe4)AJKi^)S&X9WpS>=&0zCM_rVn3e3V8hF7hnwhY_goKi z0=7*B8K%`TriE@SWlo#8Of2P3x+0sCa#5f2S5oy}+y*}Tu{2E=MsSIKQqxtLNnSR` zX`mnuGik`kYQklaN=()grWF1;PDT~JiXAkJkfC>AT@=p^c~TE$ow#rhgiY9noM$8R z{E*3f9A;cJeptce=*W8URiV82jI(+2QCGD-aN39u+<sh^J|oc_8-r3gr<mx9<Pky| zctKA*FR5mco)uSm;<>maI42?Du?ytAsqazp<T0gHklrYht}H7|M*z*mZ`=EjEpoqO z?wte|H6-9tYY|>l%~a1-wR=_Tt5;3cR=+u8wQqr2=|&x|RcDN}Y_;&48mVg+IXJA7 z@!GHXSHKT%-$Y+kWJviqUXR=U)h$Nj>kZ!OiG}C(|KqDxZ;0<)xBtmkFS?d@=B#U< zyfGRI`VPK}A?|?=+^FgWvfH7#Tl&CPC<{-vnPIH$wA1+YpMZ;CG9QA+e~uNG@@&&X zQ3?D^&&=-FX&aVa-ftTzIYLc`qqZyjwmqxiI7Yi+bee6m1#W4pS!MmQY$KQTBJYm0 z%gj+hkHkF0o?kB%di-$A(pdONw2V{?-3j!DZwb8`>rBsc*OZ-V=#3Jsg0zv|i1nXp z`ch*<>vlg18K&c5tkZix3-u}~c_V3@I5(x3KO)gaPih%?N!%~Wvoc$(XM17sM<qW^ zp)4|VL;n_96`D9G{E(&QXgs29x9He}UhRsX#_6RR5@`ClC!lNRz#FoDws+LRMju?1 zE%rfG1H6bKX|v2m&p;|TO2$@P<hR$8NJp0E=iRs)wMy?b8C~w3YhrA$1=+rtBbJdg zY1s-AG>CUC-UdEJZ7Po5^`bR@pE6RqGD33E5xzNlZJT(5bUQ!?5EB~e6NU^KL-pxv z<MTHy&GIF3e4WrOImZ{8`;AUMCX$VF?jV0Lfj6)*;1zeEL&my3r+X*7vF63Sl6Ye~ zv$UZ@Ye-`TtQD7&D|k0!F{VsNKDn%5L7VEVZti2{kQ_%#cALJQt>(($f&<%>vlUvt zS&klSamFxB!!wb69ZoG}3?Xe~4$~ZRp>|quZ3z%(@6j%_gO=wQe&FhzJzQHJ*$`63 zDVm@`cQlF4muzt;QzdnY-r`4{!yakrZ4T7A;?$pKOcqux?G)qbpTaRFo>A&Nqrl;1 zI*T}My9^MkFK!phKmWKyO7_|H3|`UK=Mx+=saeK3W{kw{%Mu<i;wSAv^IMBt?8u&t z{MpHts8miX5sOppO-UuBG^4)Xz5%N=)X3&0#<3ueO)NQa$odF6&OA#vnh6rb`QLMW zz`faQlIerswI*r)8AR@cD0eTMaLuA37d4AT`OfLnu{w`ih(AT|L?0+yd9e?|40P8s z#LdNbMa}eHu@Iw>9Hs|}AuAOTcA*rmwB`H2;{|E14_pqr+&IQU`YiS?d+i>_++57P zWprIjt}be3W`>xVnc0pRV`gS%W@e6=A!cUg*p8W*8DoaKcJ_Jw`gZsE@y0lB-1}pB zdDc>uq)(c2%u<z9C3@@Ff+(MEAVPW;$#TQazG6o`$TC$9H{sLJlUYcaH-+>%ey^du zFVJoRUtW{M6pn-Q5@>~th^-WiFj|(2F{awYx=DQQ${^=ElrNQBTNdu?v$s~m3r}`v zkbYKEbVx?L95ozRONLyI$lMo=j<6!b>aEg&g9&GDu;SqG`r@XBE)Y_{>4z~{PuugZ zSz8!{c<Xbl%MPT^RHNT|7%&-SxRGRg6Ks`}@d#P(A>wH31u^+C(*;-${_y!VYDzeS z2E(voJl)R0gf2-U-zljqlQqOK{@tnQK~4*)LezZAVKKTNvo)kZnD(A{O}HC!9<Rk; zEczV6M_fxS*k~rKF+}RKKk@evOZaA0*Y9{%(&l?A%Pvhn85~rtRgf)zFIWdxQjG_` zT2QxbMc1uDw;{|7AK>{DrWon8%^=p>Yc8V5qdWhoXnoi$(3RRE!Wl8@!(2lO&G_;S zhgHR;w^MNc!Y0}uToHUC?N6#U-dBU)*Age@nLPlrAN5}Hpm+&~TK7*sKqmO-Cn%7D zG$<Gv5Cjku;F=idxvg*?1{eqk3KR$kAJF1z;bdXQpyy~}?ZjYgXXIjIV(a`@syO{$ zEuD#zmII7P!B-*OLCaorYw!8VqixB;)nq<E#8ozmXOL2|nJrg|?WFY83;{WV)z{uE z1XYW<uKGODW9}4r)L-?Flb42FKsK8hIO2MhQspN4c+l*7!Ojj}EPqD^{k&H^m6eQE z(MI`ogqOo9Um-!GyaUSJZsF6&Jcq?YP~@40f7?z==+3+3Z(1<Mt|_>FtfjvuLUhYu zW2JIG*3zc1d7GUNM5>vhtk}&V_v-Tn8Jp)P$gr4DxBoQB*+hK6Z8mTm$wA2hN@F|R z*B&0_sM4&xz_=rnmk-2y8J}*On0~Vwe%f~4xi8Zyd^y&5BUdI}`#YC@yea|&Py6re zdjAo`#Y>>WC>S6hL?0j^^nZY8Z(wHP#PH|upJaO-Eyp!ZjQ6XGP0$yL^YUs1FhZgX z^~?HfS54K(?E2)QSF6Z;q6l>UdGF;LT75M}=?wgza;9WX!2Dp5$+R>VuDe3igc#8N zq)_W#mu-jy?{8c%Gi<UAL=j;yYHZ*$%kri|omkXj{C<~|$47ftJv^RgbmpOXE?Mv? zCOI{_a1a?iMG0x;irb}9sXv_-<O_tDb0PWhwILY~1($01!#j!8#=8NVy047IjIi8t z0JLV7iS!l3x@5k{Gn`>Utjv-Y#|QRlE6nO9-i??m<98<t8pW)ICv2**&pHJ&H0~(w z@vsa9SE1u4R^T!94PPdb%D96kk~W-vDGHV5d|D)si}CIWY(d_e?s^exLsy386S;1~ zT}GAkz@y8uMOYy3W`-VeW|HN6_>`qrh|>l}z)t(?Xm9KqOIgGY3&~JC)8S%WKJY>f z{H~#T&*JEbjhiyZDu~MO-|P?5Pi5F)w&zg~3DB{|iaj_UY(@v0em#s~Hh+9dfrv7h z>fofd3X3pNFEKFp;Uk||nLO~$nR(;b6jNq$Wn6R)I&L>zZC`Q9a4FsLC_VD}Z5TID z+T0#*+8SDA(QNb`r`Z}6L+>a+w|Eo&$COA1_-EeS8u|fd<6s2Jh>fPXVU%Jg96!V} z>4VzAdnCn5VKnOJ9J)?OEp;Wt`GY(I!-x-^9(pC?b(i<IHMhdY+8BNagZM4inlbXO zPqpW>CoBj*w0lzB;NN7yE`6HUT)dBOyi(8G>YbC8{MF{<avAjOJJT{p&$tXawfG3q zO3EQErP#~PQqw%f_%+L!yk$qM8u;^9tYbo8>m6q%KW$rhgzV;srdG-)Sb;2hLs0FS zm?UU2<r2SCErQw7wI^BOU^;`aXkW6$^Jjzi*ecsrb+ezdlS|u)QPh@<XK#9Dm`SA< zp^{ghQ0B}`zwu8l0_rdDm-=>hOlqKv{5r1ApGta)4Jh$(W2Vdn5#76)L5Ej-`2qx; z6>IvY_@trO|2+jQ3>tUOOt;aqyl`&mn2VcfxPz2mSb%s-i%AEBBs?=;{)NS)_Y!t0 zARfa-=($o6Ow`5t+eoZ!2R*w`ZS?}!F|f!2>x45L?)Ow%E_qQz>(L}7E9-AS!cHF* z8A=B<$VJ2KW7DbqZ}%9o(g%<X(H3s*kBrOy<wH#ssANx|a5R8b9deACHHO&YLo8}s zCFI89c|5&5uIj`u3^OEpr*sxQi&#pPc(Q^90cB>#9_JggXrfoDjzQvZ9Rpiz^`@!7 zxntrZ5t3nN^1hlanqGb&o;^TL!9s*}pkX#C&Q@Kxu{fT(*z2U~jM)xYMdt)IMCMk< zO9nHb%B^FlkV}A4|A-v2UFz<!z(0|pmMw3$Yi!yx?9O6s$^BobwXCeqi&OkwKvaK} zw}Zz~WSZ!^Su_VQ^;HX5*ODxnwXu^g6RA5nhqZ>P48!qV7Dj`E(^Gdj2G}m`@NE<^ z)4_XMgkD&&@E2i|r%);L+IZD2i6)JsQ4WEoSj2E+0oHx+jq;F0--v)m*n17^C*rcv z$AG*hA02yV#8Cy2Rg8fPy=TH^?!P&^dN92o7_Qp7LxT3|$4CN~{SF8PuT|M#)ERNn znVbjGY;=HBv^m!Vo6^K+^!n0UD6Qe}lAt`gHs)_z)5R4)!0SNd$psWDGpApdN7X=4 zO{u-e&|)bd4?K~0gQlAMyE`q|pA;-um9~7}=Er@2O1;1GW`G?M7~NcVtx4f|Cg}GG z=V8R9$fvGvxYKkjA2ykym5K6@h#LxRlE1i~sG@%Dy?PEvVpLO?hk7Z&^UTbQeopro z*$cdGOWgF{Wb(<Dp^5b>$9P{mxaHKO<&+=Eq=hFww|E|zBf?DVd$k2o>Lm2vD*2T& ze>o!`O>26EdSagOAuW*M%UP%zr}z6r{pfoM8zKziMPcxQG&LD@=P-3qSz`{bL^jk| zUibe|Vws*Q?tH%5z9%`l8dLrBlF{%gq?Tp}39X@#91-p7F1afEQZBf>*!dlZ;8S?C zeW7NcOB=r@79m>vU?~Glalg35Mu)VbcM~F}-tZK|cODD|v*5a8q719Tq5}!h)T;fU zKD;)SVnnIVVl|kPzV9I^qVm|0WQ(+Lmbq9oEFn4(iy9E>Gfz(yNxMiWB_tyrLzMEJ zXN`9DvPa1Z31bn~2oE6b5~mhTUV3?QNjF}R3t8XVm5W)2_MoV+;6`v|3+IKT7zq_8 zM*{xA6^zwBwo6kKSBM(>`5QO-w`;O3%t=YSZDd%KyC2?n`PK?&<@Gt)I>>D6M@!2B zj!e%%rI9b(utblLv*=T>P~f;{P|V$w`=&p)f1&kbi$#88trQ5=eS@waQPan3)UvU3 zOeG>_?6C*6n!_&8i-;tykz;|LiGYsO<b)+o)K~AoX|6j+Z#UIaijiiUnp6yzNR6;! zc92zSAz~m*YfrI!yH@|nH{Uu}Gc3fy5dm-Wn}+&i?ndglXTw-o#O}pZ4F4xvW3?77 z6M_=Nq~`au{7~Me^;+6ggEN#7dkr|j0sPT<#~dmKcpLg6A|Rl}LsOh~8M>?TWFqAW zgW~&OPVckwgTU%H*zC%F)#7{~d6AA-o3qK(_#6MQ;r5h1J~&FK6;y?$JRvq~FJmZS zBv2xXTd*n^N|N2e21Y7VY#7622C8u)4UA%EV2>sne-gtyL}>;If+MP5hysmkF8&G! zV&?roo>>Sup|{Yq5b!*UC3AhjgD`MoS=`Bub+vSL@y0ZyT768lv<;w&pNUoR*5Jl~ z<(K-WqDF*V?-Ua(QVecsW81;P<VXL4Z?pjppp1rQ*|r_fSX@e?0_6Uvb1)<yRq!98 z@mN7imBHbgc%6n&mR!1!Qhi9r_?vWIgriXQ$lB`72;hiXmo<s}BqfC%skYxCfb^wu zoAyc6q4WfNTx$j))Elg+CI%rgmV23nObkVoLkjXuUBCGyx3x}2^Ig>Iq1b)x<WeDk zv~zT_MzuS2$3N2Eku9TFVoE04M!Pw5ZC#P%9z?R~*m&oX(h4zLMgo?_n+yQcobj3f zHr3!*tIZ>#O(^d1TK@{EX2Vsx20Wx|OmyF_*-pE?TIQEgPpX`1IdkooSx?GTFc=;e zg8XWQz+*+h9u_>^|8~$I_EA3wh-%|Z20m+x*>w^b6}vWzuY%5IVG_Sw34DfZPsf|C z5C}cuYt~QM+kFK?S!jYEsbziA_OEiYkQxbH=gtv?JaI`(eS_dzc`PKG5LA|u{;H`o zn^+Zz98*VcA@wAfwB4B+-%vNGPRsAMHxb{v8Q+Uj@|X%0EFAf)to^NPdVz<}Yd>In zGjOgQ!C5Z_in(~kk|&ZEx-X>*p7T<2x=|Gr^{F}+)CI%Z3zb|r+v2GgeBUhd+Jyh< zH1A$F3N|C5!^@+_tCGFJZGv~`XTM&Wb@l0Q-uYU{_@u{xjNdG5j16-clPhA#eP2(X zx7A9$CVfX?kn4qm{bfj*s3T@HStXOH6)R|sH((eJ{cm6e2nYDTsoB*Hzi>tD6}^Gj zxDB^?+}rpy+b-?lN1qMVZ}Dyu@q7^e$G2C>c2iYYz!abo0tg6jd;HfF;P1EBzb69! zc!&Kn3;3~6XB)(bG<?&gv<19D@G~KpR8^)~Qls)7nARQ$+}c=6@$R9EUbVd9yKN)V zR@|pyZ0WScPXhMAJ|6gV%y^BZCs+II(6}>?rZ8IaJxvx*Gd5nEVCDB0NAH$Gse7gK zFfP-^-GWg5;%C|N<$G6VrpE@C`~AZKV-$RmQq#G0Su<4)v=o>gjyzp!(=;`a*2aCk zT$VAv=F)s<{uixrT!HctL15O%A;;v-so6m2Y+0Oc`PVFd==o2a>S8W!ryR}^OP-Tf z{H4%h)-bZ82&W&RyAIk~WL2tJp|=jB9$F{ubfv_A#Kvzh7+Ea|hwNFd@rwvq0HP=5 zQjDJT7uf$am&$c@|Jn%v5)usvi1Z&o{y%lM{)Y0Os&D-PvoU?+>wq9q$QAe&@p;$A z&!5ezOuyx>)$W0X+hv0$5m$&_9_XPA!KYJ>n3Jc{R;Hd!Pd47Y{HQl~v)UcXvVMI1 zHkgn7xbaKv^5A*rbQRsAJj}-jCrrI9E`9y|@^<%<D(95Uj|#to3cYPSp4v@2^<?pN z9?wXxbb)h-*v}E`s9X1TZ~dj*R`-nElWrm=*Kg6jqakx>uiNo%PxmMKG@-KiC-tV= zByY)0?T^g5M?0NMqHig;ofXqpb%R_>Wk>AvDX*RwS_ha;ABd*5nZ2FjG8>HP?Lsmg z28<UFOsZHi=!1>D`kI12^WaPs2G^P6wM2_pp}o+)%7Gn<HlTlQL2C9V6&ouXu!E9) z2R2O#3>F;+>U&0(ORAMF=9JIBi!J~S;%AJ!htp(G@J7l*T!aI!N(cxJL>pBZw~^nm zk*8|m3u)milpL)Mp;V@^w+yKt0<&c*Twctxp{ft5F2<wf{g%&41sKj6Ed*DL3Ri4L zoark&yy+aJFURqt;Ek8RZ|O1qT6SHfL7ebdL~j-p83l+_L2`yFXM=c#Mo>?wV(4Ob z>0<7?Ps=OVf3p6stmFB3Kw^~7@qb@+jp?t6;J%KH(;8>uhp*}*A?HeaLROtgw{&Ji zL$RdeuI9weSI6P!#v#etAz0Z{O74iG(~nm+K_w8fa12R%qr?yWG|9Y~W8YsuF9FdU zexEnnl3VdT^?Z*%@*DR<gV!@SS~gxAhK=mqW-TQB{9bcEZ%mD=K=vQ`I0S8L6Z0}V zxUFad^`Bo)PAr=R5f>|d>rZ>z9c+MCWjxxSZfvZuIKNCn?37mgC@V!wy<qQe?|J2E z>LPO#y39TjQ17cacz#R>58&|f_BRy@x6XMPtpibTYpPA7`Ev2NFq@g*Fgz<kv+pMo z(_%*sYZak3qjzFxzsQI)dv5_<y!pDhS5K_7N}mZPkH9;FGuw8-`CVT5o<!Zx^R*-n zPokaMN_*|nM&u><VxEmB^oI+4S7Bvl2}jZQhGFx0PFb{%EZQG#GOAWq`ZL-6eyex- z$SLfro)U{_T~^~Is~5~){RAK0zYq-_q*i29P@mbTE*(@?H;n6fHZ+|&R&H3F)z{mC zpQBWeWaqMl6Vx1}3O$nkAaE78rTQwNxG!7j{9B>!;)U;VQ%H0z$rUyeg2$P)t?)!s z0zZe+roGB(L^qT@wS`au=2zT^xyfgU_$Dt}ZW;3%L^!s$!%HmRE6ys+y7%IfJ6~re zf?cVft~Ohoxk1_@>aE1_4(zRz!^_49!)HvNNDj??RQIEQf|TytDVSet4j3q~_;}4@ zA7N2fRAt|FGZo>8pG4P~GiUc{Oz33c-nX8fK)tGO&|*-o6j9HQWBsq)Yox7vR>1~v zVw$*b6?R+=0s`l1-#?&k{fT}MyljIh<hdLZ3gV}`GG{%M?Cp4<hCy#RsnKD*0t)=Q zuD)a)6GN>L5r|Mfiv-8?3gqWP%pp#mfb0l)IOBa$vB6#gNeU{+ehMIlJT<T&48^<> zkTG%?sr$0lpGYFk-s~&vph(fYgTUM(4mY8u5Xq;xJ!INjI|6(8bI9XvW9a^2uT?7a zC)3CK^RYS?zhbOz7)Y7^Juufqc}t@i7-sJILQnNbORiSK&;WxU%Y3ZuEaj=!1ce{I z!D4RF;kjhLfi}*Nf$pi-$LyUHr>&F{Vn1nlYhr;UuiZ=_#i&q&Kc6;(8tRg!9Ni~@ zSVBxS*8_nLF9q`NF~(ef(sVFM^#_B)>mUlKSEDN)U*`Jt5gscH-WSGH4&vve1K!~T zkhSaaq$z`X4`<r;DyuCo^-yjkfd*+EO}+TP(h9^^@S>W(aLw;U96^2x$`_qp`w?7_ zMG&e4xa3I%H^5>}7wQ!VX1f1|etyuQWW4d#2gms7s5j$n)a*{((YXE^1?&;Kp7b5s z1}L0pG+WC6Ef~Bnwpgz3r<GPv+hJV8_GtpxR6z`W6S>%=qG@GR8{OxYsg-%fa43QU zpI~K%9fQLUD&EAw?UJ=0g#BKX=r3+US&b?1dqvdv?9v!56pU8tQ$1AHVKEDHziP2a zVT<|)M%szH(-mEbdb}F(>)B~NFY0W|bRY1&Hbr)p$ztXwszDor!7()6F=MnVsQ7Wy zJbVYRd%KOQ)MT!OBm0N6M(x<G@f;wmnepek01t)4BH3l_I&wKuZ{N$sxcdxRFLfXS z2-y7&%403#CF;?^qPA7_Cm8T8zR$Ci-~LIfH-}y6yyEkzqBwgE_9`g^0%Th7dfJbN zPLi5+LEtpdC_F(N3fkJz4$3YpxBEA+gP{6}-53amwNw#}svInjhltHyz@piRT)%er zAS|YI!8zgI;HAEYo|!({GhA%wh=jI0aiTPnd$=hr>u&tuJN>=Ksd@hsN_K(W)F|+7 zCPeI(^qL7+xv^^LLP{~nWx%B|wZN%YY>ij<dB$dO_`DQ33Cc{2J$V;Civ+k#!g<#i zA{q<z`Rgbc!>=ZnIA83iMK>&xq9-cG#eL!rbeKGu%x-g;Yb(LmK6=F5X#=Zx&Wb+b zmLrG<Cw$PvHa9kNL#swf4etuzC(i}WlAOM}Fa*;ajyM!&?l8G|^30IuA3CyIrPS12 zb~`hRPUM8?a?e|CNA=YfL6=f~zZT{0cX;-S3SAD8VlB+CLdCzSx!iq9uI{|U|EkSM zaEQ1Js33bcqVC^dJ2Ve>A_EI_mywyqnP)lKefUAFd?5kpuF32?&sYX3(qR@N=hA+c zD^kEcfL$1H!4eDKHz!HD0pn%0>IK*N2o+oE)1SRU)`hrsWfMw2APzGizFu0V8ymx% zmui6KGD7*)%7>HA0}O$OjQ2B1GO!CT3M~-FW<(Hs>beUL*P*~vPq6=JqL*<74&Lu$ z2uG))9OtXEnzGg04h8>7S-R;ikfAjoVlXZzapK%=A876Lm6^LW1**CwsFc#R@qweA z9~1LbI%D^)gd3e<tZr%}RX&L>Xnq5fKQ}}EHT9LBA2bD!(bI@2=4x>&^*v;VgL5p( z=$O2_kBMeIYGVhsR6>=nQFCp6^`ctnk*K)?Lcm!8;R1mImV-h%=}6;dITFq<Q6anD zTXO(<jRMX+?*bJh8KYdx<2u#i`pddMU0t!vK7VSE>i}fd?P7-xx|)LyXP0#u?PK~+ zgM<k_i%IhKq`@HrP!i%i-l-X};Rifo8XNRPMjOXAHH$4Qo^D0WOpwy|gXVQ6S+TIB zv-u(<$Yund`mw5?mr^$DJK2K}<*Nph>o>9Mv+bH%sT}I2Z4$Dply{*)nJ%-LvaJ*j z<L#@L7FVqj7RBBUqy)S+Gn-O2Y&%)|)U~q-RSkPtX#%d?5krQ>8*>{D^3;)fy~`(y zBU~^vGb!w#gO|@4sECxZ7b+Eeca$9-{%QQ)f(I|HVJVF6H+!Yx&mbXy|5l_q6x#Ur z!cv(3G+Ka$q;R<J>`}CxRow4tQW@<6C0EP))99TNJu}c!cX!eo-3pc~J_u6jy#T3H z%epI1qzeEQpY7${9w5%+06xrzh=M0C(YgQOy16Xo>(ReZWb-NfJpzmGc$$EMRkFaZ z@xMm2V)1PR4Dpx1rP0rzRpQT}Pnx5L9l1MhFS;+!*;RJ+DxRO9pJ%>nd<>u0l$1KT zK;OFG0@MocAT_b?AoHAkhUK4cA+IMF{~n?Bu9zQzTJJ-!Qt?BO!RP~^*W}a8M4A9l z%Gq8HK<ayd^!I<Wj0RM^`?u@F#|hO}|4!ZRH{deu+#BWLw*cM3H{f;s4q3)d=MP8~ zz<5l5Wd9qDXux>eTNgHAEosxafwRA{1r8<%vDn55u}ppv`u~5pPX9ySge_nS&?nE) zXZ<hM8ijz*KZ&A|1Q5lABSXfnG#c&Xu;*nWv!LNBS;}^Atmh#NR~oHK<kNX<LzhCf zb<OtP!QRP(#o}Sz3lg6O--FTH&pFhOP~H!0#{{q`^ky@0qm}ZRa9Y99*<s<l>pN@t zYUz8NBB+E!Pl6jCWe_f}C%!5VckHQXt~`Z092JUMM6UdjYFzA2YW7kQUB$JPd0H|V zydwM_rh!}RH6q_?xfk55Oy|e9XDiIQFT?y+SM#<$a>qbd5iQ##M6GKjE9+FBH0w;1 zgk5TJzD8Za($G>Zak^&`JpnEJvEK>LxiSj~`)-W6bMD0l4);Z_JUQ_<<-Xf9k@z|} zznw13%+xv1Qk>Uy*Fshg8Jx|&WL3a-Lil{K3KDjfI5|s0k^Xx6Qp}|3NyFp+N<j95 zyqZ9_eXtYSiaE$C%?uWc@rnEgALdQ*x+$iPTZ&Qv<_29zmBWG5QO@V}vAT23CZAS; z{8zXj@Y@{_dq+CRr;t=8BC=6-w&Ip9?B)sTQyXVc^e$a|;>QHZmzp!Zs;~pX?iNvB z{X+B&HOderPj9CMTsVGRHa#8qQa@k3PmwR?3DqNnHDP-vL-x1SC&S>GK4MN^LDm>S zy-a)@5GXmt7vBocc=>2<`a^Eaf_|DwcZ)nWzivJfq)lV+x#e_@<n`2MA9b)cUDo^v zt9?fM)#(KXGR-%#6u0+^ru<cIyZ+oSul6X~kV^lUfp#mikM78;ck2;vnqu-(HkU@S zF5DdoF^!|}@P5Fa^=-bxy4%xivU@Wqd!4HshzU4OPb?)47%XU~WM6>rVr5C{dOy!; z%|UXfaIe|Hxii7VVv7MJUa-){@Dtr5iV3wZ+i^)*+p{Cr{kX@6MN3=+Ck=`)p@aXM zy~ahqX1~c<P($cS{B=yKS<L8KT60u*rWrhZJ6yXhzXA4{JTAfQs_PyP&m4Kc7)XAe z$(wNCg;jdMv2S2BWxBP_7S#Y*+xLz;L+lw<TYEaCw2yqUMAL9kWxL>LvQSO3=0F3I z9U#drb^26fixecslSza|#5sBcmu8FYPff)Vjqjue-|CTwbT}<qoHb^6OeK<dGOJzY zccgChrgL~65*^^FeetLS*W9Aj{3jDQ;tHYJiuE+*M+x)87#KT8p-MJ)XxbBS<B!~_ zo%v4sH2VYxK`HQzzXk2sDD!uRf5dtwo!EPa92um$`uTzPXpY548o(&Swz~~rfhFC8 zB4jb11e3VhJNRx!Z-}Oph;MnS?=r!8V&C4}@4}G3x&IijW>6k*vC7&7X4ClDBagA^ zHrrx@=6#_xbv}*QarLr!J2oPc+o|c7nAt!gm4dE{v%<2a<>3}E|M*r6smv~8L??C1 z6)Q?zO-k5DHnH`qQ_RbJyRC2ZHB{32p>V<i)iTCmyZ^C1Y-v$R_Y11v^kd;v@VC|O zo|72J$^A$()Fu1<Wl>o%x${+yp+#2&y1E)H(XzJ3cO;Kr6QGek_1@pHVp+d0Bgf2S zqv~#UMKpu)IQx~Kbs6}agdP;O1vxO#(a1kL(X(#$+!kDNZ0d!#U%LC{=Q_SRSgM-^ z-<=C3w-b2w0P0P$WlVfLj(Q0Oe9kXx-0FF_yN8k_A&`9|AlOh$U$zUPvBUpB!9{bw z)SPA@tKmf<hv-I^DB38w76%{3jkQu{I0xzqj>G)&YBv*O7Vw)Mc}wJbcqEg;Y_+zr zG&D!ajwP)U;Oqgl<P`6X8uN3qVwVY(t6$%JA1fNZTk>vO9`9fD)s@dWuiWpkWS}%- zR;n1sL|wi#G55-J7^g#546@>^zGg%@uNX4xv&b$jt6GZvDsSeTkwQF-%wxHTH6brc z%<yC8Gwv%Hj+tbYH8QqD7a~^=W3Zid==w4$lbfEtEJJ&<=Mm}kb*U{squLTquItM- z*z0vb68SG`kg0jx=8R?5<0cnVYn=6EnFZF+5=CV5K#!+9DMCJes7#ga{eyGwS=&3K zKqe1v_Vqm*GJ~WH@n$qz#u7gVGVwwUWY)}1^CEf8H1wEqy)NT^N)HT98mzT-eZXMD zh8<tj!S1fb#9tIVH0sg`Y}55+a=fNsh7mZh=2T;5mdl}V`uM%gi>7~J1`ZE)(o>RU z;OM6xpVebTSVXV`*xVOe;sn)bAz!<A^hx*$<gso`BT~w&Q$~RKc1rAo=6-S7W6)W_ z@+WHjV&PDWr1u88WR0Mg3=vdr5CSh`yJ{4NWeZhu+Agkd5{?~$8^C&?h67dtiNWqV zI(kmRX#so$-XI~Qk9lp~ofcU*7M$KMvo>18q>la~wsWY5wrX13r*CO&AQCTB-&^Ng zT-b|@JE@|IP0@6cr0+FoFT$w0#0F9++(5a`8`Uq{NXm`@iI*aojM<0`I1xER8%6-E zA)X3StgoV$3Wg)Q!2XU3TIQV;u0$Ne^@%z7JT!$iOX_QxjznKYJo&O<R!2g=MG}~y zpCKBWNHS$F7+M3y4;a-zy1|IP{;|X_jOoNMk@*tIfltWsS_xTj93miFMU)0|<P(|- za8w0oLy`X5k^1r9F%>O^qbUv3k-;55vrru@2X$+}EBj;gK^>+BTnr>sn9jpB(Re@t zE0MwLLY^3*s;PElvT!ugc??zIcnXAXd%`K99BBdtZC$~5m4(4f)nO})jSo#F<_ngQ zk+hNF92-csGf@l~MbOX&V#_qT104l*k@nTV0@eWoW4eNka!~UmXOO5x8A=G+i!qK) z#`;^GB7+arl2{6zLycBJfwZk4H_@MmQ-|U!{?8DRhvLsf{>6@sF`RC21i;V|8JrEE zs2UOv$Up0%FTwg(>tE{A1PTE()K(UTyKNxJ#Bvp-3C9O+Ll+|A-~q{@A_i#?75d*u zMJu6s3L*?78UvRB_#=b;1w%&W{}_>{sE`f-e$1g*|7}ZsB(3l`eBh;Iq@XvCzuO06 z{nsOj;=khqPbVV<VBZF|14L$s#0Ty}@B2&SYARv~mOs+y{zGTc8d#o!C<BT6f3_Te z5kRm1zlaRO4J4xfu!Dy)guST)xQeEzkO&-)A=<xau#Eht^)G$@^4kB>_B@<86rTwO zU`tyuq`zFp>}z;0t)jwT1N?)O$+ABd%WxUFkjNG6Z^wgjFCqUQBO*}z1@P>2u{si> zRvfHBj9nv%UN*8pB0t?f?cjj+mM>8IrV_m@WTN_^oq1FMk^k9|zlQh0{gDRaA38Ig z^~a_IL~fvT1q1sA7WBHDK=p#$yTDMI6w)MWw-qliJGA{{##VB!l#JUdUIPAP)^&!| z-Eli&F@DNsWW~E*crll~?rzo94tKV+A#~l+ZMFRemQ5FlRpcfc&g`Uy5i5vLD{ydN z>di#%1Fw2#supxRi1D?y#2xU;W0bxYRDukvqNWwT%!&$Nohy=bF+6|0jHSY~qT)x3 zs!m!5H=dP|Gr1Be@&4?0d~=i%s{Ye3^&p#&9IvaqTG)FVfuq>2==H{TPRl4?UU8aN z2G-j{IH~_PM#$pK7g#W6_~BE3-5pk)r}x-(kQApQp<5*&HMx>dG#*JEbDnWk)!8BV zCpcEs`>>u$`WsK;3;MUjIJMEw&_g=SYcMWzjN0E_>ayBaTupCYXwBc3GZ1(&Pklt* zLoR;$>|avzJrHaFjwVdvh-i1Oz2_UbEA2k09(_Fl;mhL!egg#YZcXP0jgHsvX1aI; zUVigGiKN8rorLX}Kb5<!@N%meKlj8QS4Yp_U&7vdfzXHJ41pfM%i8gpk4N;QGA`Kr z#;HLj`-F89o1N0#xciCIQoT&Uf?N?7-;=5+0|LG4{_^@<Y!BUg@*<GE&BCIkvRO~Y zTycs6bZu)hsv;+u_#%t#nHgBxmJ?Vpx+6D++g=lKmw+G*ISXvO?1sL}g%=`VhxOA9 z6>FT4b{U3fTa52j!lOUPXF0LXx^SLh%^OO0*KMtvn~(27ReqN$L1DD&M{eB1`B`rZ zC6ED+?2;=?N<oXL1Fx#=xFk}ATcHKv_L<jI1^AcG6etk~Fb&AsKd}rz<+I==>)|xA zhkpJhRxt~!iq#osWeyn@VhVPK&6$tTx?crE_L<9|T~UL@<xR+sTr<VKDsDX*>}*M> zFGLj<Wsw%<q16_ST9@hMHqo*h)34kPjQ!?jx*nWh!7#s$eUPD8RQLwP0HF<=hv@8H z_5KXvVY&T9VkZ+NI9y{!o4q!6H7T=aNIMNgkQElsp^wmP5>yk5l9hRr9XK2{ZX1nF z+OXyK(YOXaujQ)I`#F+~*~H(r-&9qC27OZ4CGd_=ul3J0a&O4B;9yPiWLhfCHAjxi zOgpL=gMc<uBEhh&5wMA*8>HdtF5Vrm#u0+BY+d9T5D-kf*vlE<4a02sQHYE#zs?<! zkq;Nho;*0O%*ZIua43_*KQA}AueFWv)p6#Wps*U~({3;}Qs3_nvKZ{TK05E|Ctq+C zJ<ckW^fF7vU?MJeHELyCGn!YL>L!VW-5RO{TQMsPtFT*Phj3}_8o7C8p-S)dVNv$? zs#j<jhT9MV?FV4lx&lNAUCvQ<2Z*Af4uLV8oGhK2AsHjLF;o`5^xj_0&!e^L<l?oC z8Wkm!TA~(>_A3~;-g&_Kv|i1)mWbtKLmD$t>-*$<iLEa{9y5SE!_y?;T<VzFl~Ibw zkj$zi&;yJbrPQ=X+euC!Oc0TbjH#BXSmbMFPrHJyP=odjUY-v2L~Bm*m&HP~KAp@W z@##rUGktBO!vHG<z!Ao*?d7QB$rFLfVi8Gm$VAs)D0}i!4qRNG4EIs4IjLM0=hM0j zG7FI(T5CJA(u;#j5DkQFJ#bQ_4hJzQ1P9cqGz=oG@rRCpGOeHI=;FcE@tN)D;tbA? z(4ZQ9EkL}f7qGc#kbt8Ja8RdIz6NgLBNz{4i5~&cY<Dm$LN+;8rAC~Wobd8=WL)ee z!KK~>FEv(%q{k(V*vZAWnqg|e!o@?D-5Gr?jR|1W6ig8<?H}|5IA;3_2hgvCH$_=0 z*;~T&mM#rT_Ia!ozh%vvOV*TQPAwEm$k3*sxzCfxs+)R-_gquF3P*(6om8i=rddXy zbs__Mpc8r%5q9pD)WJ-U=!FIzxdVL^BApsG&4&aas*?QEL%~ca0zoQx<al<+6)Xd0 z;5P#sM0ESnVdb4|kXbaXJrhFg_`ipZ$O9!x(^*)^6k-T!s9Ahq1PKdfL-@K)RCcik zh5&8#Dj6W+Xb}meCRk2S2lSKOMV*>w-Vq5jQr{G4zs?l`(pI>o=VQ7FdWh~ww@Nh? zABbE9YO($fKoAHZ$a7sLXGIhf#F>g|JWT|P#7Y$$6G9c#scUQE6$MrdCGgQUkOfl( zA}Bw#l!epCGd6?Gt57dH*oF<K&qyE-WKyV*#x66Y1Z8{a_h#O5P`8CYg&D$G3`rQ_ zvu+Yk9)n?@b=>4a<5ileg`l7n{joTIo(!yl4%>v63_KIU4mAxHX%p|I2lPP+K07Nx z&(cy7nGZJ)_;DURs);ulSdpA%SyTaHJ3+~x&ZTr{I1_7Ja8eFiP_<5BLI@j4hnYkc z%p^}Ojagwx0m2et9OyK1NRwdPpE3#C`U@rKwWR96|40{jjx3%HMV)a$bP?<_E=tfc z8VoBBO3(rs)7pqUxLy)fc&ooq;4uMG9TKL4G!b+ji)KYm4$uU2K^4I^|I-D{N<B@~ zk^3W;AuLOaWZW1W*MjQ`4~C%JTzw8jcg#Coa^hcA`vVNK0>ocR02BsTMtwpA8_9$@ z2%t9*0TuZ26BVc|oJpQZDcm2+<iY)I8D=2AWDpCD{{Pr9Pc@bK*PsG~rt3IRMP!I3 z!npr&BDTd}fJvDA0qoy+{SS8mn1w!+`InOdgu*w<#ajA^eO5#h!N2`QcN~aU38wbu zXz@Py8?!Qm=YNorDn6CCr6ejJ?*2dfiikP@_2U&P&@yTaO91!<(xycKL*1V$L9_#) zoc?zx%Ps#;cG>nkRwAMkv7JN`!N&S0XUPm>Ci>e^82-!LPFCXmG#ON&B>(Dbz)yu} zk`pM40K1C++l^3IpBz9O%FI}=456S(JMX9fA)5A65>*J>Uw|6-|I#iMIEyk&$pINb z%>M1db)ha&q@{muIknn^)H|g=&xDv<W=2%qF&XUie=~J2VX>el#CR{72zL4(K>zm} zwirk&w3ZxzNW;G(lD<$3_Yb~?b*X<W0D%i>!YC#H(i(Q+Twj}de1Ur_(T<hH#tm=r z(kN;Me_Qr%YxwG;FLUHrx_{R0ofqS^F66Y*&<m^FMpKT&McTQnqQ!}zIQ$iUG5bEx z<v>O)InpG|Kw|Trl_QRJ>w&EahZ`3}%xa$L|FwvJ`Hi%$1ol8YTais!)ugNHw0(VH zXBoYaCE*J+aoXm&U=pJ&vYNmkjy26aAl@CKlb7IfxjZo{yB1g2d^}>*SpO+ZVmB4< zY{5tD7fjGil*=lzL_(QKd$>gbd*yWu^Ww6l#s9qQvcK?>G@pep1=#E`JDT&nIo<(V z)P2Ojw*_N8Z6l>L?LeyJK=y1N@@8kgSpY5{A7l!wwH386*p#X~(Ia8d5ka@t>y*FA zF|W5gwJvd4J!^C|lf8Dy=aE##BktC!q2K_r7`bWmqvx-foa{4HB`37B<hN@q4@AoA zT%6e<I=Hml_Pkz8U60weBLQA+3^nI-pR^LQ7$(PBz7pLsvaVs%ZHhsJzE3Ksyy9&I z6zP0S`TXR;crFDPKBmhQVcz5tSlPtOi<t%`a}e58VQs=^tgCtR;9(t^RA`Qu{N_?* zrOob@$VG#BU7P;L!@f~X@X;&K&;;Bcn;!!IaW@!m_}&aF3J}nQ6VQKqcp&rN`3#@R zxFd<5?-iGR3W*h{j=|~K?>U%>AYkd-y?BZEPbXhfF@7}^Eoe!(zUgj!`sQYAq?j(J zx<W?rLWVh>6Pb3H0ZwHGtaCNIy`5d{DP9;Q{|vn`?y+rB@4}FMxzXor+p1wWbn6i6 zdN^bFd?pmoyEna~<cg%YGiT0SxL!7H*7HlBE)}@An0!H;Y2~nr3415a8FIT*6Ck)6 za&sH-ZG3cq+V#rP@U~mWl{-CY5FiUF-Lqf?-ss@OemrbY%UU-hU3Ktdg3v-T*wjUd z@pO`Ldtl6(B5lewozoRqt&vYM#~>ptd|?o9Bk-IvPkq<ovASB?@T_|Eh*T_<>P%m> zIvS9g&+sGILG94x!HyNLylS7hx;t>|dGkh#5pSA!e_611RIcelb%g2aHE}gxTOT5< z)b;An>uD{!vIw#xgnz)k1KlEq_+-cXNw{ym2m=a<<^;jd^y0$W>6iYEz?a}Ljm3<P zVjsipn~dX2Q__2T)-cVY<3dPLT&dN{n>%;S$TXqMrGz54M#}k^Ij3y#7=d5bjgmlj ztm%NCIOdIi-u%1I2ici4p}faseX0rM={Km2<r=rx4#<tOy(D}CASvF=ro4r0xyO>V zcAK(0FeVp<Dha*B<)M`WxyPrH*ENlfSI^Q{Bxn~aR^^?$)YtU9=8g5GZvC9}oV<sZ zr}KLk4vM9p6u0T=)o?$3?o$#cFmBu_gp|KQD5+tI(SZ2ood%{qt&C0WXgnTH{*F}% zD|vcto@vbNP}@`GR%a?db7w*xRZ$_gxXcdgE1eQ~lloXzayyuO?SWlm_yC%Cj^F9y zNpE_Qp}-M)=c;lrYnUqHxOhl>rDPHLJ+>FuTk-HZGh@E|89%8?`!Kn1W72+=#<SYo z87n?#%ZvYTu<bUm@~Nw{wch-KxOe4yosMgM=#C~YKq39A=lgB#gu@cV7?AJ@wG_K+ zj6;D!tJSm$EGWazS!u$_WgcPSRx8t64%P~WmGx5qRIp}Gem5y-g*H=<e)y^ojPOwK zshUG`Ka^6<6?;j}e>F-`K4m}^C9bsGdp>U3>#8t8yPQ;aqKxL1*8F!QqU)~l$JWfz zLLv3S!H1=}BRlfu5nqVFRe`a48p<4j#p5C0Qz+sL(Hggj;ZfJvyM~r8fvoG6>pV2x zZ}A!9U(#yw7eb-ZLax2l9G>)2CG$PViv8xra&~&eX~`*kei7sDtyr<7@wOX9X~}x6 zU^RR0oj&hLDtfK^^qxsWIyC}%q|-Y}tf!`lydNWmoaiHP=nFlrEg*zE>i4;mB4+!! zhw2|$AQxoQ6tcfQQbjXPO?fhZC+(;Vrmso-)D}`D-|}D46Tg#pxZSDez!;0A3p>ER z$t<!Z|9L(mfEt*igAWJ6i8R^FFxpV5Dfc=QRoCDy5OFw);=A09r@YE0^)>{1tItRv zHx+Lxi$um3<Rbktu%f*W&M)4CUW&=Ea(|7#r9&Kd4Y!z+STp3-l-w7UcBIHlz(79m zd^E+2tkG7*Q$QA^IlMCz#d?=($=cvjet4;4a=Q!e<zS!Ov0DX;*4RTVzyM<<QMj4u zZsBr>ie4x8s0j})joQx~lZ(PJCz&FhLei0X@>$5zCmiUQm`vVW7)O~oUa+22L;>BS zzeBevO{PI3FdvS@%_r+ieSt~Y-}Pg*FvWYbzz|_NJH<<ZE`TlE6lnIlmL}P00@v9= z>?EUNwjA$iC;8EB%>e4*vMbLzpXKXAwUhVpC4}p!E9i6+V%$c&!^o>ZIaH~gVxuCv z^V}eDz}+!H3SPM>shRHAsAOj~?!{m_=~0-ZA4ImgMO)h4n;Sy;zjtL{FP_(?VreVc ztNfed<Z)kj?G$O{q?^4u+gy#)5mQ1eqtRT>g9fHzZ|^r}zDElc-W0yA01t;l_zzc} z*NL-uk-z%Zkc^HJpO8EsZ5$NFkZYpoO6iVof7_-R`6>=$bQ9#v1%Fd~xhVU}f}@~t zy0Jd0P<C6%3aB4_mI)3c-(tIPWOwbv1^EqQ%`K7-{F5#7Glos~W;kvh@#@GgX~MSC z5l6tVMS<6k4{ww_kX&2*XnWVa^zLQ*RDC}4&evc3?)3^crew`Hlh^jH?b^l3V#78I z-Z%Q$cy>5=K7`;md1)m#la1ff3~LU&$-9QLQ22;4o`#ulQln<JhN4?fEc$-#!jx@o z!x~HJ<X2%1uEsFvnT0@kW!QzUlg>qswdo_JiX|l#ysFF8Y8xC87x`&<yY*l%BZl0O z5)PUzAAQ*B2@6T%KnvV{UUfmkiBWrQkViaQgby!-movO<*rSkn@`hJ*Q<(}a4>y+k zpR~W8h+a!YwNmib&0lN_+X`Ihxv>O=bo3zc`7<0aaHWxCbzP)>Q#yNkc$9vF+0mOn zY0?1aMpR_AYul3Y@?NJ4$|<s*=IHm33sR$y;zAv-Fq3x9p~D>rrrsBwjWD0F`N{#w zm&S#Fh(87T9g-i3@<L15f|g>pwfl&9SOoXNFmq12!$0Tz$LxsHj{T*hEmyY*SL)I6 zndX!jELKsA<hP>J=?CudoON?&{Edie;z4;Dlhk47mxt~4uEL<JdN&DrljjXJZV$Oa zcf84@>E9say2JgHQL`P<ViKgkpX*D%g4Rr^-wP`46+gEm<;<3n^-Jsjc42J~7ZBgz z%uW>(bQx_Mz-rK;x%Osoo`+W>az?i<F&<iXs?YjzW^61%aj$`dVyp$%mGRTG8Pvut z<6woRRbx`m#27U?J?a`2c02WqOK#iJHL0?bi6U$l>?}D;n5Gd_04|&@rK*?g80P}$ zq{QVw+3mSfjUS%<t_o(3VWeTk`=>S6u)=e=k3~iznM%wTD~Gj~K*sy@gI8_K=aMmu zg-F1w{z8FLg33~qezA(zY)GZey1myIYZQ7!i)03?JySXXxVEeO5fr(pT?5JIf%P`X zaOZ7A8;qCkO9XUz#Bal0y#o+n(fJFRUs@e4+3#9WL+M+*wF+c)$5c1?-S}*orV#={ zk2k~{JZH>~oc0M+&^&QM1p!GMO^p6L%?H6b2krc5YhtJl3Hy$z>o*zx#-i}9d6P<r zptJ<)Kbz=+sq)5=LaM?K@8;H92_V!3WwxNPg3s1cYix!b=~wSQhZzN16<8s}ACmy( zsASiTW*8$CXyL!FnNd*ic&L;_s|jzLSkr=7WIbswp6BT74yHn`b5=8vnfjv9!v5CV zy6J@J3mMP^f5Mo<o3&haWS=dp!H1~C9n7uxB~tjdYTy?lWuBo+NS-n%8Ek;uEr_48 zCQpo~P>4-!y&B9{F01^@_Gjj`ZB=QVtxaIF44fEYb!7fAsslxZzj1JHPnOG!gXNFA z+HuQ`WGNrEdo_&<@*Wt%TCpA&vvH(w_bifBg!dSJSbc^!>KrJY!^*&gw+eJHIsI4P zW3TR>PJ1x|ZDb55(VOeaQD7@wh2ND3a?PBuQAV|4*BVZoT#iS+69&Xe6L3sj2L?rn z6JK4Z`5<;Kkp_19?|8FF@TO<9_2rmb#NZCYk+GcWJi%aKWH)Mdl^_vDh|FGr)s*Ll zWS5!**IX0GCGI1!Ku3IkKOd-ksJB$K4xB$xx-#(*n@Le9rkJ~6MEqpWZ`tN<AIadJ zB#kISHM3>GJf7`iqa(qj%Hfgq3?kO_jicbxMv?O2vbN>IfA6Iyw|xjsC_DokBYm^Y zpQ$>EPmB5f14TO*k}H~DoMc@?Nh$rTh&+s7_|Aj3n$a@@A^}#*w<V=0T=%SCL7EGx z#+D1%HdTuKibvHCy;60q;rrY&7q<WvSD}zVBI9#Ni@+ByBj7oFSP3|=6N9ozQa%Qn zsZhBLB%#nqZ}f7c8{q^PIX8sMDR4mn!p+{gB0DA?ga(Y;;!zP|iHIcC@Vy9#loRJk zY~<K(9Fpn!9ZlW|AzSuZe69>xnp-rNsktVMFb;!e!ge937UAL;%@Xf7id&|QeE(hL zlH|izRQ`~dg4aqx={D_htu6yKnT95T<x~6-M16_=eQai2%_rp<edq=|;u{dh^)T|? z&-h@4+Qu&$AduAYY)g>4GQWgH4@emHNX`P18EnK$PGxt5(fLabzxII;0SmE+5S=?A zw_~^l*?^-{*D|HxfWsVcTUCRjQ77Lq1%WRpP?Ju4X4h^z@i^;D90)?<-9e3%(RMkq z{&Gx!ZvtJq(Q)t{SYw8XRR5Y97~)ue2qV-Ni0kP~Dd$%!6}w%?>EpzYMys;Z0I%L_ zS=;x7m_QimsQJNx=qK~6FawZe4Jg#HifxM!25bD8(o&mPSHb0_5&{A~k^02}{r4>+ zo4*mfHMWx%vDd7DNQ#C_{o$+pj-Y=p&_m!jEj4d-4$Jt(uu3i!URnRhJb(E$kV4#3 z?<4+vzQV!Gt?IKUfDNZYNL=}PBrr&R(czjdt5@KUjFigEd$R_|#d^?2-teGudmm0F z*sYcm=NHxnj-xvjyk2Hn4gs4&VeMuDm&a6hWnf5|XI^B;@sdx`1_qK>6&j9|uVOSD zWLjh694E%$N7c5+-)tU$#%9a>l4zSf;24iYb_7$L0?Nk$+{tt~4hkz|V>!^DlCfz+ zK+*f}nCsCHRETI!jm0Uo2LmiMt;S2C;1-^KJSL*Y3vYa{!J`^iCOO2A5raGM5CtHp zq_SgFAp-c4ddj%Xk1qhpWlmCSVuH-O1=GV;g`pfTw*{xDIQ~(7PL);Ge-vdeoU-#R z7PVMR%;}CyUzk#7Qzs+$+mdv8tTL2J-e@K@6gWuAcx*{DC0bd3*-w2ctTF`?T9V3B z`&fGPQCn~*YRcmBdjP1trd^5E{UL_mom&@NtASdGvABylXK#-o$X_#W)yjc}EO2dR z%{35OQ|T91+mhKWv`v*d5^uY6RNWF+$*ah6=3VL6iR)?KOtfq%HM1!bpYa?<MAxki z1J3eSi&twGr$V%(=-b;kN5CmNsXUzKut#Dv9mIfE5WBF-+?+Iy6T~sGU+P7HH|In% z<mG9Kc~29?`;hJMN0e?jzo59J&;*pzcs|fuG~tT_V#+V2AC^ZimDy)2n`&JtuJoU{ zwnxGVD41gQdSKbb?3V#$wFx7Vspz}pS;rWL$1sv-SS4IKor;9IB+mFM&b*zC0lT2b zXzyfCD970k1Gc*&gNCC5X?MVH5CoPP{3sp7z8*p86Xg6V_MwZ0H{S_=@t@}t%k;n| zk5xs}qDOkP0^@SF3hf26(~$!9fV4ISZ6O5sX3hGtQ`6I!ebN0A7i-J|h0UHt0}jVc zUDD6GG+gyX2CZtLx|Koff)l4(kpgkMN^u~Nb3#=7z@_54PUQaMh6wFlx`6!Ka9HGA zg?c0vVU)LWA7;ir^%rkwAG3lcn8}jkPB#UOK{AkCC`j<$O9+I4CKj-q;;*7va9XWS z!}*m^6}`bp__)Wd!6476H0L_&-x-A9VY07Txrc-sqlxdG=QbxtD3p}>(YQTYt0Q2M z@>~7Sft{%8pux0!Qb3?X)XcwxxJ0d?9y`u}zOTFG#Xv6QJ5&;aW>M^-vqNsU>>_~5 zEW3<tFdc{xV<gk{W`Ly$>iDBj_z(|5zxOhY3Ok{|?3idOh|(4aMsvtuCDMVQNiEK^ zO7gxi<NolMs1Wi!h@iIiJ>Ef}^)9@4cBWAQ@)>IS1S;k6=qi&Dn?E?>igm<{A(s`k z>G#(F($k6bM8ivQf)>{H>Q5oYER|JUWYB^I7eB~XvhWrPkWZ0BoO7ccB<8emA~aWL z_o3FT=!dk^y$15f<7m9|9g+nm1Lucg7-T>ZiH^zf)^<Qu_BqGN(}25-2j3;5QmCyz zK<kqrlI0?lU)Pebk0!!fs^f!h{l2IyO49kGGO#%PmUyQertmkp25^qb#Pj%$RHHeB z`4q~9#_lR?v8X*8u`R{!Z*m1qb(ahj#cSh`UATVyU{QiBfuZ4f;!33{#rP<e%mI6~ z0WA&pm))2Vc7yRSVaiT!@O(JkHUC;@o1eyeuNeJgy=6hIz`meiJnP8CgQhs|is2y} zeO|Qj;P9ZhQvtjlfCmB*>}n_&r|tp~qUXI2u&5Hf?pSy<C8TvqE-CbA!JzTiR6)wf zALTaj{`V&0tsq6bg}EP5dd3=Wf}b&2J#YUx)b!h-KpqhgYEo4E??X*2e=?@Btdw!3 zQbT*}20tKW<p*Kf>PFkY{buC@hGMMq*C6#em&dKA6tv2p{n~FkJ^b<L!(3UPOhpD_ z)X5TafY6apFL&71bu^ho<@<hi1*jif>dlRdv3F6jgfS`3Pk@FwaI@fg=j!{+Y{UNX zvexr4QA6>*!kc@EzURT8GmXQ`o@2vNb96VxprzH&=i)n;+hi9SWWhv3e{><_(4eZV zivan&SW4XA;=S{%#qqw=s=@y{;e2pnm>8Gfr6FVYa;MReuB-ZmduaP1)B^d-Zj}Z9 z>HnhZoPs+G+HN0CY-?iMwryi#TN68(*tTukwr&4o8z=8~u1=lzyV_N|t9Do4JiT`J zde(17^XkHbBaPCcXOiqwuzM?0=3bNUGm+i*y~%Sc=dj^yXIRm!1B(@>iylP`PcC9Z zvcZ7|_hjoe1?d`WYPipyMeAd6!Z&Ay|MTfwj}Mzwt|h~4;I_l`psG<By(A8@p5TV~ z>pne-ePb#i;W(M~<#T@b2U#O4G_u<RM}NFx*=TNN5h}tfxR3csg|DYOgU|EPin&OQ zamjGc%daJqf5Z;ooMDMYFT%})Mu|`FqWSICR(*8xcUZlkQ>T*hNuMP%aT#^OPTeWZ zF6X3SZ|6Q?zn`$|`Taxtff}Fw#EWuWFE+g<a&^_#_1TlV>;5wkE)8hGki`mRY0LIn zx8}3kq#DJbn7*qh1=-cnxqWWKcjbLUvE<pZ;BVH1y_HS!@bku8y}-nlk71XDd@^}a z*E&C!*V4j1+oMo&@LC^1evvw5wUXgl!f0b)p9Qs88>m0Rq(&?ZKiBFi?^&0pcXwXD zf%4a9cN4&-`$3C-yiZIj3!}v-Os1l*aQ<ifw#`%5OAX(%SY6Neg+A@Q0zY?p1R7yG ze)*Vr%PSG#_$rh;-DTucvWCBFyn=-#XY(6O&4n-WNo=5R#Ij0hZN==pq-aN;ou;L8 zTs%RK5et5Cw`E|>H#@7xbLY3t&Ie$o4su~Ov0GiHTju<u#@EssRtpZQJ_-e{KdQ@+ zi?SG+W+Q4Co}l=5CM#8Dr@3^W?jo2RTCZ&1F5m+O$V1O7`TAng^!4N6=~$TWNER;0 z?!7=eD^#grA>RSWawq!jSZbztXsWO==Jm<pTbNHNooX*is(sIlvZCirza@40oA<lk zZ81^neMKmtpstb+U+3kXEeD=n+F+SD>#I}V<|XxyN;NaO>n8pmJeS4y*O#M=>NdZw zFADrW4<!*L&9w$LqjU?8;yV-ZMGyFci6m9wfD(jJt@^$D6U@ktmVr2sd2)Gx)*W-p zuDjwUkM{><+03RR`RDDWi7!J>mQPH)q2xE0^wszCr9Dv#7FqdsLkgPZ#n9z$hw7=+ z#{u*N{zC32;JJaU-uI^kyl!pGED&H%#Cg0xTnw+^6pv4EDS!WZV)%C3=u4b|CoeNW zy$477c^;|B`z9_lThNdd0mCNJP{MvVrzt$)^JIFl?SpyZ)E<PNvmIS@5rX`60oUf_ zz>JGOXPVU?-!1G){rYp>^aRd#6g4C>vt565+|kNY5Oaf9--WtEL5Fh$HnW}WIW#qO z>BjLrpCxaiel-T8$SYWozPFaqSe`?c;qF**c4sShcLC>F-%uibn}CD5#;=f-0%7G@ z@P+4PQFoF+Zn(AGCJBCR16jf5L-}z|G@p4ysO?&@@cq37m5i<u;Vf!M{ymsi#+B%q z_?WnBX5G1gKqYb2h;JsMP%^SMllTcM+p3!Npdrp@$H7?Qs6N7*nH&pu$K=<L?z!A| zqr|l_f_HAPzvmdP_?+ZK``zG4&`)q`O#jR!dBe216Qx%yNTyfpfO>Hyo^~_v;;Tx4 z+r2kCY=7DwY~+9N5uSjHW;&hr>1o1AtBZ%HJ**}^I!v<U|FVKVU&G(+)7B?=K6XBx zalpO>=Tg}YD9Uf}U0qN1ofz1^8+D!7>2xwK{#PDoDK_qOk+pRda{I8neQp_`c5e2# z25)o-?zbFqR<~^WDD!x)rXH>+JTCt5-t{)OOs0;kSK1cYN1v)5rHX=uGJzoFqIGBD zajts;gQ382rt2GLaFn={c&j(jdS2f<zHs?`e1H9-Q$?us?-HltFZ7oU%Xfr%*NrxR z2VZCUw`kdx2U4@our_XX54K*ZG+0Jr<kj@pm)P^t^jbHM)$%j2RtEp)qNR;kMt}W? zXBYZ+byMR`jV>I1L-zPO&8UiV4L_ewCQ8TsHBSy|LmroJatp!X0crGRlF-3jWBYji zwkrzUgJ)F1U$-Z~yoGmcD?O)ki1J7k`Zg`|BA2Brb5hoZyF;qFf#v!_P}P$1vx{lR zl{}|InZM7PjOS1(#Z1*VuAW=lih7@bIbILv+OFZ)*w;qZK~#?Gdti*ZPB!+RQixD; zfaeTGMv%6dFXJ7*ZQD!kmHe2Ms-?XZbC^~JjONJQFHmHe&-TrmvUsOQLn!OJ$`ja_ zqzt{fZh)%L3;WiC7Cy+<IZsYY0e_j7PB-m9R^j-G)t=6T{lA2G9V`<EGwE%f3i^;S zg;}BTV2z9B#0pUoK}2v1iuqNTepE23>#8)SvtVP3^jArekKTyR-Uk(TnA5qTa<5J{ zIqVV1%vTjBByvKDMh)dI^azGhOGQiL>N9|CVp(t7J6Bxf*?`5j;i$phSL@6p@`!HS z{fCksL4XwPzShywUPjjabV$^|-yQ36`WFiC^PKF%_89}c&BJ>@$}zNkHoDS?DoPIe z-Fo5q=i7c#uzg8zC&dwV4MRTjB~D5k2?Y4;%>A6?E#)=ZQGF*G&+~=_ridi2eTEW= z$O&JIO4MCGD77rsVM*DEnM2~+9tbZwv!u&|%vXWgUFR$Ua@?yTwufHvot@ynISb#x zq2^cnS^P^G2#;=klWuN$-|-LU!{%j`ZKg?(n8YX1<gm#!A&)r!6i2`e%c>lJ08UvG zJy?$d83xZX6y@wlRIwLPdEEBMRQxS{;3?7|3-3PR&QNvr_{|EN9JrUb6&k0!P;tRd z)gG}qkmOfiJ;Otn4DAo62Fdu1Ri~~2Ja#?wIV55sY%bisC(GC(UeuxgchE$79jyES zMG>hB;y?ZDYly$W_7|RY5<&P48Nf}loUCl?FzmGL7aH*d{cUOh4Obd6+4;#tCC+A9 z%25H!{ZOj?A&z0-Mag~9mznz@@im4aFZmJfwR|``vgi-uJz3E?SlA?*Nu5O{&Z)$P z{#hy0l7`J)b+KstVk{Tdz3`3!O_M`oqK8n81$8d_Wrb)*N;NWofMk!w6_uPNi$^A& z6a%n=^e{@HNI{Hjmg{J_Itc5X>MUIYfL0W0@){*pB)&)bN|gV4qg!5~UZz_i_681u zUT5TT6$-T12eQ^!z~0(pN<UNZ*o+9?Jue<Zv#os05RO1F**y{=fr3OsCTx66)deWt zh(0Qifux0ZBVMa8dL|WGcJ8PGsV=nYg@(LJL@d{6m)s|v^b}bOTs)#uT~cv(0>$ER zL@-@*)No;rAXHh9buc+Dlww%y@cfbU_lgNnf2@at-{riBP{ZqsA#HJ7H#c6APNY@o z>(Yx5R&)*Qro=cDB?Y@hRzYAOoDG9fW8Gf-_3{SW8%*5l1zy%X{_F;KI%xlD9<9xk zapaC{=yh?8<GN?rona5f#FHWW8C1nmGo~7K!KA|p1Jix}%Lk&nSH;1!!5`5>tAJ`X zweJVw1s6o>hmTol0mGNFT1R=atdtvZkYAyQC1oIaE}6DT75wJdYDoU@UR_ksL_L-6 z=fw_+4-DZ3-B3#;GB0FFVPIM~oU)i^4w<+7P#YKRRY%+Il}svB{htd!4rwXBW@Rze zdod=i(oB;o!r5>rfiyx9YYVINNi)?Typq8KwD#*Z?yg8TZ=uP3R(Tq+=KTnu=1HU~ zk_Wi1Nuu5RWn7W_Ay1!?4bU2-vfe8bxeA9xYIDGZe3&CGE#{1DNMFE*7>O8imE#nW z+_%me#HRz<0|rqr!*%iycS>ST&O&{ZrM~=EpSq^N=HzZHkZa?D=-myhZ}l&=oAWNj zEZAy2?_@(-M^Cyo5ISCW9=Cnaa%hTmCcuL@28ooI_4`ew(tj|0zmN#O%#DDw)j}+X zX`#F@Ux67AJ$=H!-qcEu7wQFjQ}}}=_T(94vDiH#O(zI*(S-$%j8hNwcD07YOVW|r zQfuM-uYtSrn2l}T+YsYyD0g&a{fXi?<hPjXZU#r99#B)#lh?CXftg7z^euEplCc^& z<KVwhx-q~|BV>Rp*pk@H#@X+4|3wD+)-t-Lp_nkXV9X^Ltkv2>2yA$C5TuLq*8amJ zkn0n5BEyql+;C0TLE7NW@oTo1r_O!@W~CHJPbpSKwW_k50w5@~pCT$B2pLSNTG27W zA0RuGVXQ*^5T27LpI!)MA;%;%-LYV8rgTLEO@(CijKSDvC&-o|*R-5&QZEb&kmeVr zE2qS&+z*4TrEZfYm}Jby=h8=zRSFm)9W3JAZF4HN|1~nu;+#fOid#)D``ci3RMQYT z|ITf#2=^E90?q__1bLHSdB6=92Vo~;(aM#pe@HcI++TcxeTmh^e$wzilB;q{tS>_` zHo1t)G2xvv$kUY*5CPVuSWX<9j;X_~V(%R-aS*wh<tkKlv8(KWwh`i2SHg0{Wq_{& zw~A2?nTtpad>u45sTQN24&YRZZ4AbJyatj*q$6xzT|_P}My+DwR(@+M6Hqvvb7C1W z?~?Rm0T%b7f>g*=Yv7yL4dyR;D<q+(%zmqXwA5%_-ax^!iKHQlCd5`#Fz>SH%u^CD z$}HNjmV3v+ET$$gG~^vi9yT8$ea{~N!bgoG@jZ6590E@YXeXJmmZ*W?*c=o2t?A<u zv>dL?OH8y1YENmGTwysS?2)6P5P-v0+uyppSyC@Rkf&}hfdgbU73Yr|PTS>UO;DxW z{sBBWYW(Yal<yEUHBP>$x2rxw$f9mf4uZ5VwCV85e*t~Rd(;rL%fhex#3DqU7BdVN zDqi^p@oNOPAS;@@PqNJ+k{Plt_;VegfRLuso1$N1q%MEy>_~5NKGr7@Lgw5yIo7yB z?1p9>H5*z6xN>8U!BcCCb73idgk5{+-K@=*DBBZb*czhM-5e!{mlSUkspehNT4sI~ z;1EZy@86SG7PnD=Me1`jq96*6x73H9M~5>Izu?TnApnK##Vw$fB84~L3EK<<CP~J% zQ4q+UH8{t*i%<+ezF#ITiNJF|?7$yT<2;EV#67lbJZAV)oX7CW$Yzi4<9;Wg*q*Ef zrQM`f$%?RQu1wAJ6cChY4+1IfZ@qPN?VcBiIh35%bezln0TYfF5h={wcK8_HF^VK` zG%>oDj|0J+b`+xJP@tkzSlPPF2ho9Hju6ZQr$J3u2HQw}j4|Q|o}9UUlu0H~vkQ^E zYduX>+O&T$PPy?hk2{T<?>KeN`nQf)jR2%Nv^E>DM>|;ICjm<8ly|{4fDGJK$b8D= zK)BvZiU)VQS+0t+x-d~Jk)!Qz-DVl<iMc->6a*~^y6Or`u3Dyx$10o>#8SSMQ_qC% zg~V_c#=->>lRxnUoR$M5UsD*SF&WY9`i}XKCWD!fiWHDA+vX&i)zF@k`J~LzHH;$_ zym3Bi_weM66}5BYF)SqDOo5M}iDc(9z2gOG@t<hIxk?~OgIPIiNr!||J+Sd_SHEI4 zEvL47f1@}iM{*NopsF_GAJ{WJ2O4Oa4CngMun)+*dcCf!WaBYdd+Z1fN#I2;rCk+* zE4iSDHQMq-nQDnQxqKjrWkO!Ed*fMQi)D^rq2G5e^pTcJo1}iqD0;?%<lk(Rj5I&7 zy>iUE&YM?I$aqcm?=dLkt?o$6^zpAK>*w^i`~u{M*b#ld--B5d6foK}XC0C_xDTOv z3^G~6k)8d5uXUUGCvogLtL(rg^`tstPmYag_o=4K;xmU{(9??YCWcP1fYo5HOCGxB zyI6On-eB40_F-9UPKSZTks-JXn?|^sb=!-#SK&baEb=JtqX8FJr=<OVlfwHBIp7QZ z8o;XLGt7?;XngJu6RwHbsItIPfO6l=;33p=Dr#5WCW|OGWhr}$ZE<C3|9vjzHh~Yq z`(5~em}Vsz&Y{=mq6ieG9$I<~%p&>E3Ex+jfC{_S9*o<TpYYoMl3;t)!c}?o6TW)K z_rISavHZ^|lD3W0`Z(IxcG?$&?Uf8L1WcFo;c#0ZoKG6|i0En^zYD5Hply%{>E7Vw zL(lWYnN4ss1BF}$*`$scV#kTi%>Bh$L%W^tcfB)C3<)IO4!5OLz^XN(-PsJTozJ3< zIKn^EhTHcJ{%^qLC_FK8=8K+4Cxvw{3q6k|UB3Ba@djK8OnsxU0YC<n{{f%K;EcZG zYao!BbW0o}=&y-le24VLZTjsdyP!pL57F;Y@0-9Pyx;)*Nfabd-*&wKEkvTTEx%S* zu;UOIGc+a(yZ$6zQt>HO0U>0b_~VY2<$k?5hSwGcMh;8Mso_Hl@PcU&-!4lwI=mQ- zxda&bMAL&shpmr|Ag{1`yX)<2#1$JIJk}b#>*}@EB8M|TF&P!_5TfV+%cAMQaVaM@ zYe#V3GdIH5?qohV4lOjVauO@0vR^14`Lq|(CC|rvj%jj4z<L6y=0sBA9Zre3tm&{n zg?T6O?Zaxy<$_TM+sSiRo3x>kdtFI&LwEtDEuWn8ZOjOX^OvO97i=8ME3$W#@9l%e zNgjnn_?WLpERMLW6$e0mcwhk?Twu2&4@lESGod5Qe&_fS%-};y3J0Ie`lo$89rl_G zpYB(}{j#y3d3zJL!;OjH2C9aJ?HYv_P`3W>Bv-yp4(^=7eXcUXjQ;R30!y464W0eL zW2t~iL#Eui07W;VUIZ^Xg#mgw?GcQGj^_h(>3{H_o;z0(S;A%BhzQ37tWPVyO^O|v z7+bX?*37K-d`<bKKgv75Um%8jULovHl=~rCfUJC?MRk~>ZX!D%X=n6gFZAV*G4GiF z#q_EM#$1J+%kYynE1B*5L8#ex>9IshKFVyZ2<veyw!W!C`TF$m`b#f^G4aP|Npxz~ z9YFqdMmc(u<89SgeEExXZJ2^|Z#WFU^20B#Xx99}e{glMHtyz7CO3fA9_2I<Ne2iQ zS43VQ*XWzsdmcd;S+^<$UlIsGnW(dA%LQ8GY%OVr1AJMpgUt@$vP-Y-;Po<+@+7{` zB%gL8MwF#(Mm0ev-}piBM(sXca;MbrNLxD=EdDnq2J?F}zSli87_AugVl<>{4<(IM zdIZbW!t*`?*i)`^04IWdchEJAy&=#`4Z|=Md>+K5`Mrt=OZr<<2rAcv^YacWP4am8 zLtJL~py_~#$lF1}EZ+A9GTPhs_3m|~@M|{?XKnkfFI((<SKFEr9Plpc_!869kUs*O zz0~t;1OBwS=J7V=dMe`86?OE@oa=gXnGaR(TdXkL#!0ZpVz1UX?BzZzgOaFzhtiv! z^;b@Z8+I=Cc(lO#r}h7s@^Y;{6Q%nPg*$BrqS;>O<4*O6MPMja*pdL~9b^^tGIkjn zFWCzO{Pr8Mbo}^B78mfVb%*_AMS^eV13p9O*5RNohsTb<336>FZpYptv|FyMChCq- zo?r{`>3&^8u{*2c1>MNsDyPL{U%Z^~@_bm2f8?OToGSlD*7M3928ESZ5)bm0QA||7 z*`<RTa5#9JVA`kbLwiZn2Ex)wvCB1m)X=d3g~95OY*t4OQt`uwUsLm!HiHA`ES+N4 z5bP=JY7tE>T=A6fA!e69I7hb2{bfz{E0ySrB4Nz|M6r1v!|zo~hlURFYZUOyC#xrI z{NY(K>vw{*<tq}W_-oyWa0B6eO2EA&Tho^R@(u995F8&+mo<&@AZ0QC;8i2w-=C_E zJ`^1=fe9N=;kHGaBa_V%rt<XWqP2cr{%?36GdA-|z#7%F>-WA`PuL}B+b8zB!|%`G zO<VlZ%avNK*+HJarj=<M{;~sSF(I_)oxZ$<ygiNd8&{nkOSXR-qcuWDye%ViOd{-i zmat#2E+q5ee%(fAuWoB@*9_dUlTq_KYv9Ma762Yn#mfl}hja(PL=y_g++z8xbT<3O zyKys{$)UEbe{%pkb^ys61)*#CxK+*n5&y!HoKK7)m)AbJI%%$@y#SKy#+tJW0Sx~A zZ1sJq;7SNJZZP&+Z12Qs%I)LF-hQIju8F<`lyK@8x-+u@--T6okwoB0wDd9HzqYWl z<gW3ecWoO6Zr}-9{dLq#X-t4ccKfDjwUF%9w7nVd@!=I*C~!U~E@8K24E9}$+IT{( zMGq1Qc!C)?IIaTUgd?@;PMTK6a^qqglT`5VkVp1xUssluMRy785tJ?}Oz!2)ptC41 zO%2&*exVrgK#mI`j4+)+bWu%KSpz~3y+*O{C?aAWfK^si6;&b?4*7OVyS<cYc26rW zE-S9YF0YITABT~>q(%k0ePGcDFN>gREgg+Fmw>J`wN*BiHsIUfHH>kq(7P(Gc>+gt zdn&`P_G?IW4-Onj<(bX+UD|XvXx7W!0f>R9B2qMarj?O<x)injGacGPqIk1Nb9<5` z53Cb?ktA<dS9h|;tKolI?d4$?9A&1$2x)y~8<m{jdv3HJ{UOeDYt&GPTs0sKdMnOI ziWc7)i)_L4xF3@znQwdwO}%v|#HuFjEx9yG$_g%i7Ov`woUx$&8+4naiNquYlZRY& zLy+nykVTecuR3-liIGyS-7JCaZyDWz`Ik3TE^9lz(w9H+;7S+$t!`b5=^1aE*?6%r z2%|Jl)lg^*lzPnHgn<VfSwDn}p@@D{%|Zv=c{uQG7)F*Qzl;sbwcEy%AiJBkIc1eq zR}$5WF|oP3tQQ>?G`wvZMmS+cQ8&Pm@AL^vvHKk8_3ya{)5;7~-Uk&LjdMR$Zu@(F zGN)ZYYkZz)42@-$v+rb(OQ9jJa+g&tB^03$k@0#_KeF;wu(klx^E~yQ*P-<Kn4j7w zGAQd4O|2Qcjv_2C4qem4F)BN?<%<vV(>e^T+ng0gMuPo6@B369WQ=WxU8zf+P|=FD zStI`Nk0bbk2HF=K>(QZX*z6EoixWH1!L~#uh#dYG0QC;`?19?&i#AMPzut{5>aWvi zxTC5vthJrr!dZ(ZCKo>7f_;0=_Y+_vQqAEf6X{#fpn3Y-rTWVgGukmKuwKELT0Jpt z+i#8lzu|674-~2&QuvYkxeMLc!2me+yZ7MzG7jl&>7&ka?U2cG#f-LH@?gR8HF0)J zw>ol+R!#{Ib9T2Q^mQShU6;yLLGJN2aSr<*DvXEHPsZ%Ko=G2Xi1&#k9!p<3PPqb` zJD4ZpQ8eQV*!Tn*y(MXr*zi`8Jh};i9ljnLsTe?$HLbAYLDg2Gl4VACu9Cov>&$Qy ztN>Dq3V$^uB)HoI*1@Vbh>&#kg^1QKqE4E~^U{d}CJ-?}!Gdn}N@$-%U`jPW=_SN3 zDZCif%g5ebt-OL}d`!J53_S$caWpQ=cYlAZKhfSQsPoXmoYncZSp-HD;*5GLP~Za` z<$2Wz&>{9bd6wM%z_{v)8!)gtXgaMi@O*i>e{{F0sht<AworfGd1-GT{5<~iwY`o2 z7^`=G;7%cN8-9i$b$bI{eihgQG_JF^JVT0+F?)P89v&6gqt@_taZog2&he0@tX_s_ z%hF5G5c^D6tN(YVXdxkkm+D!?=h|Y!m+IaAb{})h?w@HNg$HhQ`+}Q3dx7-+XF>hc zt+hYv8vYr3`q@Y1Lf%Vv_wo!VMpSCAhE2&6t~Gv=`E^!v3)MH&V8=s4PGpB}!j-jk zwOwGVQ=z9<J0-LAw1>a@zpr(D6h81*c0M8gn@E0v8m&G1k<Si4LK*A-izfZQp`@(; z5y_Eix=!m{h~C$ep9tNTfw`n#AU}j}K&w6ovw+gYM(+aN2xR4OGf%y+u<z&6zbF&? z)<@9DqOAt;4m0)c&o}hoark)Dy;cNRRS4)v?@qDXW(2*4sUb9l?(NUVJ=fzRED}&$ zGlprU;PxR4d@#-AO>?a?2!=Z)Rw2GV%5CHF7-Bd(w$Xp<@QU_Ri*U*jJgA_1p2BjS z4j@M19epHs^(sTg8N#r2-`I1ts3JX!kg#2m^Rl3cg}9ewvOM@tf<pSxWAFhT+T=IL zsmI^r#;XvzxjcxCqIDSc>K+Pza}>yJp%N*>^AawF+vh>YA%hKGkNaCLZe0Iz`x%r@ zzV9LpN#}m(Tbw^ePS|#AShPH5`wRiS?SA1~F<P^93t~IH4n?m_6q~a7s{w0#6844z zS4IP8&vL;yf*p)AI5&7(q<fT@xspKBJOt+|Lh*-~;}~y#_AAW1)dYQ)@AD6R5rVg` zcl`vA6^O(^+NbA~Nk(-@^bTqI3O<1KhPMzNK3Fg!K&|{!Bb}rW38P4`X00!d_BJFR z&1})`Mg0t6?xYiv21k?hDIpkfB}y&(EwwyX!<}WNNl@vCd-+`njg<tzAc{3?x6{<5 z&88o5rTuFVgI!qx1+Sb0^@&sx+N4g(+0R*{+NCyKP^Jn{=v3-xd|En*U&zp$2(hBy zDjA!_W8VZ~a-C9pz9+Z|J)0%MRZaw}sxU2ivzG~BZzg{>Y5}rFQ;F^?KV$PuI+4mf zD$yTLoDd@n{##AH#84r>rwjc)s+-)bA{_okgf}1CwYuF4^I1|io72f2Vz9pt_Ud&G zA@%KIy@Zh8!#@{ReqIj%BKPDXyKl31Q!dTRn+$)ghGmds*mKfQMU8+Lxk{vZh19EH zI>pr{Tp!-K*`RFM<NGbx>htu0LFlq}%jXqrEt7M_!}DFalX^h8E*O3`w-LIHV9hrX z>$S}-8CpN-Iej8Nq<?lvnAUfSg?u%&a3GMw3Xb#c-ZCp~>hD_z-ez@b>A)L0T}AS| z^()@8vvBdg)w*=)sn{!9)!=&?wgy~5ooO(B{U-Jyx{#2(V0a*ZuN7t_eGSgcns>d@ zE<D<zna{_%dDvW+#aO!LVY*zP0@r(Ys4~?=EP{m>8JBb)U8|P_pAUM1vY>PhcVUO; z^MQ~4+#bOJjJY0S+<5Hjp6T9c^2+XaxsoPXRBK2-E;sO(i&Rwgh@Mi**C*O5z3{%x zge=*w76el_2wfTuEaZ~EOT7&w(P_dA@!&zP<cO?E{DsxO8oUGCx#ma(vr#S`+*!7D zEA?NWQ`A0fJKquhTY*p+B)z-)DIF3R|9gR8`%i%w)Uc92kU;<Jp8tYCpqAAKlk39g zm5rGvlp#h6v6*MNw@8EnuNw|)h|bH<z4XmdcD+ap!+K0Xf(+d+Z?AE>Zfp5?DyfNJ z@o;|S8cQDG#ZJ&WOm4Vf)7-{?{vF;yyb$5z@jT<(i6_4jJn?U>-hRRmZ*TP{BP|1C z=uEb!Cwpqkuss@MjAmweO&4jVUX;J1L)x_ZJ5|6f2Wk8*!lM2{)okaGyQCpWTj)wR znZ9_4Yx?dzDm20Rsn$;mlCV6d!&$3$^N~lhO4`D1xdJeW_pH{Y-SIK^xZTy2F0XFe z^%^-X7wy@OA)BqXs!I2me{T$^4lhBKSE1*Uop-*EGAMZ0vR>h%P59>Fpg-`vhTALf z$HvWqT?51slw6EvB73tKLg%;=$nR8q3xCcQSvWgvcE}{Ncb%?_FH;diwdkPaM;&+h zSgq7#b%?Z{J2`4kPT24i_fzGkHbpX7_|KIbY`ej-M|*}uj_`X4;qLd<OH`XKECws_ zwI2HDb>`9{5`Sw=y0gJ}C1?>aBq{Q+6gR#?AWzH&omn3-hE*ZS%#(5L0mhe4Mj$3U zTMq-Jgvv@R>dOtg?%fKgHb3X86299VTsH{E%!W^FQYB<|QHZ!-8L(rjAx_>sH}E|F z`MerCB0@h;4A4y7MJ0XRtO34WOKyFL4|aFo-}lAE4h`;4cM!%dVwf(y*pflu$NF5m zRJ8d*mILvkQWika!rz39&CRD7v|r9BF}md=@KZE{42SgW1OaPnLu03=Ik?L?2YpM% z54VO4zc2A|ue#O-dfaM?>>nV*$a_i#P}v}CzZ^N+QzLE3a%SkBv?2Ifomt1!#ws_x zvMPN$aEBUR_d9%{DA$h|Ce`HK6N;u<%za%E-Lb=lS*Jt>X#SYEOF}rH!YMc(^|>?} z1Z;4?4!%B&9T~N}Y%Ps~iE^}eI5q5#GpBKDZ@1Ur)CAC8Zxu-I1)aogEyY~X1%ojx zpeb4X{XAiLpYL)6AI9C<?r?d&W246&n(S~<wYcXwILB+eXMEr9v4QcOU|d;v$YTmu z8{_$#vf%l=CCT>*Qcv%m&%eg=K+QerkY*O4nj~5j6hXrUuF7AiY$t=?jsq(2x}qu% z)yhztD+;kg)Y5a?bo5})H&w!XcsqRGfFlp3osyHHuWas_y^|pX&1?)KAd~Z%IOxo| z_O@=ZFhi%^O5}ScI#bm)>ZN*JJS2Jy$UlEw;(4Ec_2SgE$@Vn0I8gYT9Z@NR)9d-Y zua`rI!V-NkCtE*mh&^aGP|Rou>ys3h%N7n^StK)%V)A8HVSStX0P(B8Di!7jx} z8N3J0`*)+ES%jXVKnf8GOUcfttIjzw;4lJ=*CuwxGC}j-SSE5mD$TMQPGhlxrV$et z!pUZb2?ur3QeTB<0;&$9n+fMgj@Xg&>SSX+^ce+vKr;`%N?Z&SSHYeTA~hISQ9ng} z(n6|mP;xpHS!L4=KkVLn6agGB*pyN~sT>PgQdK5d2qcre!X`90gAn5Y7mEFvF=BOQ zA~lOCwhxn`L0Pg$qKh8(s@<SfggKB%aJTGbi{Rj2g8o9vM#A*>xKZeN$0B&B2Z~;C z+RkOF#+F~9ii0S`Nn~PJvhBe6iIBpqfkr{iziO}vDBFIyfz6%7E`}j2AuFQIBT#pe z7K!~sqNNd?h7yAF?n5Vv+XB%HmL-i?;e58-4TZO-LHey*h)Leu;DL%%YDTR%6)KIv zwkXm2?@vB#U{BUqxJUu&n%rk|`Tn_|%I>TNMRgx81^qz+7pgLOxI!tlWou)%ny`_j zb*x|jPAzbvdt*^kUM^Wj9YV7QqY_fJyJxIN)mt1lV%;j@FgQVcVw&0Zq5<QuWgqX3 z<$~N=yHDYMriUrh<R1zx33f(<Utl9x3%yc})g)*lG{3r_<Vfg<ClfR<*VfVD^x{+q z>tO9K0wB2(g-kHZc#yjNV)|G?xhYFZPtYa1wSdd;oZVy~r9epsIYy33kEHRC{%~gw z7`!Y%OMt^i#g%UgTNN$Ui^jsSAX7#SM<$;y1uItN_Q@huO(<9-30*5>ErEjXstcl~ zw(17BJo-(=N=Oi$g7!5PvNY$qI5jHyhm31j^?R}!Kfp85ET2-PD^-VzOmf8uLI7<^ zkmR#A4*{{V%xlP#k5fsJTo3=+lC}4bQZw*4>;DBN9Cb~OZnBz9mrkPCT?+=pbn28B z3ym{NOhylDZf;{g>MR_{RuK!D2!&`tEvpBI-2e&~m9&(~`w=-1YpqKIat}8w+-XCQ zd&_{!%A}>lLJ}4yC+Tmy!%CRcc`A+zZmo5hPsUQLh)@a{*?7uB>t$u^Wd$ZrC|C;Q zy2t9kEThRlZTYJLGs?!w+BSUHljcSW@*X#yDe=y7bQRB7q7t=}#T*fRfklZH)(Q_% zzzoSV97C`i8}?jI&j{43?rOr{F)}VMmW)7*u)0+G0Z4=oRw6j6M3k6>%wXr4$LSa3 zO0oT)-XN^N;SkDZ7#tZ3vD{E3*LR^B;Ctu+4s05;RBjN;nh-d+>y{ly{)+1YmTn91 zhLA&gI+Klpp5|yZ4#dttuv>i)!I|EoLO3(Hpc$J&_+o@1q)UbnR$ob93$P6;fB1V4 z@4e;FtDrg=r@3_2Z#9Buj1QmJX-s;~vz755L*dO|Uy%PM6yhNUs_cLMB2cLRccH-k zAEB_RW$|O$ME>kXddFwk4oxzisA@;3xdr>>AOd6>#}o2n*)$I{4ALTm!hb)1R>fCB zqcZMuOHCWn&$ueDEcbHnoSZ+;>Di97%vk_fr$)7$u@F9Las4(<`EhHieY$=Dipo}e zeD0rny7;bUjsec(I5P{kUTOo4x7HOtygBlEO&dREO^<~fQucv9mJE}e<1vB`3$q+P zI0%dPd&K^YNQLEY0K&+l;Z+4-{K7gun($eftqU+?Cdt-CC3;~RxPe0hLnzWz0x0Dl zaWz{R7^6vfvSb4w%vZe`r%%rVP8z?CU7p+dCC!d{ng+J2+ACK)cE~WB_@`{iAJ^yX zGhI)1#M}ayui(TBj_kS~&b@9oGFG?&<|^E4<vH*pCy(p&a`I|elC4MSh&GL-E8BKU zF)`<t`ZuD^3~Jg~9|mrmJ=5X|gV=o}x-^Jb-m&mC)h9>x7Ga5r*iujj;dP@g1=h8z zT#`X1iDO4pOF`?~YP>rmPVo}oBtsAT3?v3>OtdK#ZwnDoTUkB4-PlD#w%&D~1$%`G z<ZbV*IupKL$<ra<vM~)4p(GGTa%|(puSDOmsJ{B69P-z>PMd4KJ>Jb5qu=inPl^X? zhA$1vYhN27d9=^6=~Kl$xi~KCIRmE#_3-x{AQlXpShIR;vNgjgM~c_FzMc$zj3+pz zGg=xef0oDGd+WE(C+NR!l8h1r+PN3M8htqje{mgrjVOA|*giMMv}fs;Z>$oRxbhjb zZN(I1&Dq~nlw|!a$x6^W>3rV1DMYGVdgt1i)z=kBH!aX~O!j_7ctq12rJw<jN>Y%| z_bQjy;7-}QIy#tr%~K;`2G4~k4}f(>q7U1*uGq&J{^gK`pd+%0Q*+4N+7|q=m$s)& zeas1-*nj7$^F~BMM*(>vcx4)hdeL{^P1RJ&5j*0yd0;Y<plm8xPzq-fDq&>bxcrVn zU-iUf$WQ;1d1G7K3&XF+LEbO8^ZRLfCyVticFxiF4~ur=V?0|9MqpQ~=i+y=T+Bj7 zj0F;Wz-~a+r`GWIW>J^>mNOXY0MG)hy-OW_F%zASu&aOFl1E>|`rfNr)~;$2_4sHq z;%Zj@q}Butnu-&@o*FbsCbDEIAvXWui|NZtElfjXkUCG4h<XZ^^vHusy*KyF(*n^4 zxFt`PDSHAD@KLjU{1W23t*y9s%Ol&OQ%$tFh&aNuM3NF}-a0Z-klz=FXgEDnMMF9h zMxRqzuuq<6VCH4T(@Yt`(*bHwtvH3rCgu<)qY5?5j;^92-{b<)k*^Q!ZD=wLd!qJ< zi4gP@h*c@i6c;lgDyrDRw-rAM5;*Vr%<t*f-$&YlUth9)CAwm^s6c{{e|3Pv9d2<L zyLMqK7~kVO+G9+1+q||{eKu^I1)cALmaO^2f6(f8!Jc*Z)#p7FhUx-g(|>!C{9pFK zz6?2^_aw53?~lrWlfe5ei4SGIf6RUrceHC>(|WZS5-So*5CapL-;M2E4OdSNC3C#1 ze3u*^Ds{Q0=W7k74Wxa3pKeZdu3d-yRjrlGcch;H?$6t=Q?~fY&P<nNx%#zP#^&?_ zb6bzNhJ90~t(+%_Qc$UZwhTbb+d;*^mX0Xk<!Db*CR6)(>b$S85bBaWt0v*=Wgh8f zeVU@9OC)%U?pKvsb$PkYy?RfTqwb$Cmzo9V?9m+b(6@AO;+nfFF6we)8pgZe!+4lt zhd+7em6ga!)+C`Y!JA5h^{{XefBuNI#R*7^+TmYZ6ZYwrM75%Og`D43;cFxL-gJNk z1%Q+tsrP}(d4ebXRlUn+p*zlFkfjM-vY@}27QM^_z1dRYrg8D@vFs=v2^aEhqI-rp zyiUCJ4fEjA+II`H{V7HBuMgVZMO2()I#_==Vy=_P(?HptIz6+z!}#!@=R|~&&`^Jo zul3j|9$d@4t52kuG&yH_%Xw<nm1v;px1vksiL5A5S7AiaoJ{=OH7-n1G<KvSvx)5x zU9j!^QHXCXP{RqUvnf}swlGPZQ&YlIIJP8Sv{9mpdq%CS882_wI$EpX=5+UcYSy+P z6|(p|NTe1|mMUda$acAu7{f&QY7_<9FQw<ygGNo|f(+S^E-gQMwE3dIY-->tm+<H- zmE)u0R}XBq>{KNl`WwJU32h+Vgs6kKLG%)c^a9a^CD~|5a1`lumd$=od}tUBvOoz9 z;?4(WBCE3MU620S)i@|{$(S$0y61<1M;uIxa!&1R0Zg%8XJ&;)D5$HcCD30~7Y;Rm z8cv@V;9(3L(XR+rg8LW_d2uSK;C~m)<OfPFnj;j3j7ov2fk-!ZK^hKPRp>?^DL6%o z3%>(ONw;Cp5_Z~=`YK0F+yu9zf+bt&M;3)T$0lvzX@1z(qB)uH8$uIN7vw-GE{h3< zlxa^&ReDY-5=-4eHIs6<Ec#MK0XK;d6WLF^z=c<`u>+-6DxX9QVI1S0WI2h!xrv19 zPY^Px0dv@VCXzF=e>`b16PwQ8-iM&s0Q)=!n$725&CPRCtsSezc^%0ZK;^`KjsYFC z{z3ls`mG?kf0{MQ&HJDviszxLqc`yI!>F`ANP&hFX%PpL5@ON&hyqZBJoZK5seqh} zSD%SK$f#{vXSD=m!4wRTkml$XPqPV`#GruEB?zE0sg1y}V>AlJv(R$SEfX|k?^x*C z1Y!=>=LL-#SyVJ(W>CO}+_Do<1i>JKiNu(|FnHd5I_24rqP$R&I?;)7`XCRb_vC)* z>N<ZmMu{mFvDk_t;c?C}NmRo`&hoJR!unk&F7?}fS~Q%^&0@>S9)#vl@RKE0@}Wxb zVz%F8w+kG7dIFUh<Wt60w?!FL${6hGDxk_h$qr0Y<sl?~MM%u(FCq4J&;~)o7j-pf zX%x8BWkY!`Qde(eKs+WqwgntC@Sjm+CW0OrZ+V2Y0wM+(qte<%((ZV@q=&LQCf``U zYlGCDYuQZOgd{}YuC2|ESq}jtQXE|Ieu%9T_*v(Z;gS4tFZ3E6T<{6wy(uAZiq)DS zgiwn8SRk0EyAu$_m6n#lIx@^Q4-XMVeg9nTX>cR^DoCYi(t!a(OpP+voaEjuN(}&+ zpKa}I$a&=mv)bzy0T14agMozPjofc~@xDRCw#fQP0ug@RY-)+5CGw<dg7H8zR0VZ} zHSqEbp!P8-BoQN+Dsh&10gV+ST3nV<vm|jPaMIjHRcIQ#Ot!N)Sd&E}oloGQ;3biR z0Z5sEPQ;Y2Sze(j=w(BljJ(jgY1KJ(As?0KGQuG{u(e<L*9o1!3p84QTN)sdN7tf5 z?XilO!~z3pULvpQ&`iyR6cjk9`c?_XYB?<3pCJzM;+C<!kS8%7QJsg&;Mi+Rtyxc* zwS+hc+A28LWm#N@f?dZDb7}?@6BS_(X`V+-Z9|`%g0GM+-R6G9U+*8Y;|koQe00AV zLp*_RVvbOPEtJl8HL8;G7ods0Mjdy&@5a`g;{$2Wx<WSNSaE+4UaFdt=C|&2&2@B+ zSe^VeZQZG;UQ)vk%CYvo|L?s;f337m-w=Obf42X-Oy&5GOuf+kRNmmg{`xe%@*RCL zrJI$FB9X4|RBspdS2Bg(Aht&EX&?KA!GvQ-26gHB{pM+`-JD1P4~D_iQp^2$^Y20H zdgR_m();rse`R1f#h3@Hu;7vnPNE|z@(nPg@7%W4o#o?Gv|g37^YLj{6XTI!!L%(a zzF^Ff)p)aRUcc3yE@$VnzMlS26Mw!<()#;h1VHl11NR1aRC{~9&;3~<mi`T`7{x~W z6`a`R$5zg;lU)h>803ME+{j87fJ9;>WeCyJ;`<4E!4Q$CWq7QX9lR}}qHl@>=dxr> zwkF5A#cp_v1j|=)-A?asjR*T!;Y*iGYs(h;<+8&y*Q-m%C*Z*0JeOUz`>DH0`(MJ= z#u!T%-PPPj+2zx8PRl`I(~V-KYKP0)<*$a{kqa9cuIB^#iOwZEA*}(dp6r<L6+KyG zzA_U%UG7~Ou>YzKE}usFPAj+G3RifOrD7T2iG_&JZe3xVf8#Z^Wd56J_@^nbpGq-p zL}olHJk#)vr6W^Q|4*`aAXY>~Qj#-D=-Bhao2j)k9eqX`eI$|ziFz6=ig@M*f8Ixu z3koxd2=$0%vUAp>WosJJT@u8=SMtl_i;)D9GKRFwwCT?bRED>&nKP%1qvfT+4jcsg zt7H7y#E{O@QGzkk3p1w_JrD7vuYc|3C)*|-{96wO3ZUlo=-}%X@U;f;g^yupyX=$u zPTh78Yy^o~*dyVP?D<NWj)_x#3kPE%5kmT5O%yh7Ty6BdPQJ5ksE)b~1}iZ^lNQCQ zb^5;83P<d3>t{YRLE9wAeershT(is7AbUG8cRcuf-|T*twH>=#I=KSl5*nr|%~<+; zG$_xsQcqunla#~<BSjD<B1cd(0QyL3IQHAA<F<=Nlzl7);j{@NOnf}uJf0@T#K;ox zKF?oG|GDbTO{KgJBpX@{)Mdag)R9`0TtCM=A^2=@u5FBdy;z(K3`gAcVc_l4^>p9Y zu?PEyxPLZog`h#SR0)O-(As2f7abfrI`8>*raP7F{G&5IoSG{CS7$b5GMS#l)!<q~ z*S#J?PpIU((AWBxvG?P>VB2oM7`X1*mCyM(D~-i~y8A8fnHCG5bft$h1dpv`{_8rp zX@rP-eM2@xfpR6&*@<)^`FA+?`1ddCfU_34EUT&k=BdW<RSZ`ef}mbZC-h*X7~@LS zwdNSHY$*Ss=)%yS+fbD}8w?t5m<LpzX!FWO-cckm7%9qCT(Li44AHr)lfS{x2aL2F zmW(u6sn88gs<x&tS$HKP-L%;Gq8H9HaJ`tyc;&QXr_MJF<WXvahqPJ^7aI(p#fTgD z&_LQ+&1ho>ZXb1!k2R5h3kB)JsFIk_l+|n6kx)eMxz@&_cy+#1jLM&CR;nJ2=&2OQ zpVtU>_My#YZxU-`iWA}+8#F^3MTPd&t*>hiU|S@>F=unz+?#rc=4l=t@3p(N)!jFD z%mtUEal&R~&_{jW6?E6VORsdtNoRrvn0f9%TY35-e0F*~e_g#|_0lzbhk^mo;Z!fY z%}fA#BIl4Y=+|N{#NCSQleQ7)n7$hpczFR=xR-X$JJ(*DD_3Q=LhRi1de%Q_+nxB@ z4bxU9Fw>8bV#I-7Qtyvj+o$?&=YljasdDid&P=nvouoTP(HLHbj?3Av9`_>A0+?a0 z@I4A92D+@n;^!&`jCSlj(%Ea?*@Mk$ABV$5kfQ94o=*yNTHB*UiyrfIpQ$8^{a^Bo zi@QogLtv|yp<=NL3}bjD*c`7U!7?4kG$oP_L*4f>HLfDCh7^?C1Vx~vNLs`vPbfnY z6BS=nLW`hOsZhHKrTceT%VGzm22tq}d5dw}83Hgfl#qz$i;)~QX@vjiGRTl9hL6<w z9gOIf(pflcw-_NzR9Mns(iKE|t(g)>P^nPhXTqRn51@?bv*Kk>Y6Fwlq&I@FGG(1- z>sW$IkZ{0JIx`tEh5VWV)O_Tj+Lxp0IuH?+W>r}|H$a9^nL&<#DF~t_Fn}gTC=5lL zS$9_!hWFhOFwBonW$l(K!YH&TkW$820e^W%uxKQAe=Oo4)Nfju!r(4wS$;R9)sm{w zPlT6+UfYooT(_l-(u@u$t=gj}r-zxBrR7F4=h6so$KNyDQ5dVFK@AD{<Klyd-;e4! zZ^$6hNs7e%iWE9zEA^EKoFvmt`?f^@q{0#TUD1G~MGgl+@!3t$Fwqi)l{(--!bnmS z?&n2fk&oBKg$Rk?Z#9w{jNVQ#6wAbG5a1gtKZ+C|0IK@=odu6TtoF!6s9T1{?6Yw@ zw+QgYCH^Va0mwAJnE-OsAbb+@=f+%LAzW)RA$J^0=Zct~6Sq8atTWy#9qIt}>qkX| zBW4nntokX3^uFos*1wutG)yJpfRsa)DT}*9Ae%P&hheoNB8Ivgru960C#L8(?Ma#L zMXf5Au@l$M7}b*}lXA68?$Rc`At_@KdL1bF<x#z<A*=1)O+!8-lNlSZ<X>hJ=-nO< zn{S&``8(qZb996QL^W7?`Npu7h*IU4svySCmrbL*8Zcp)_Q<M3AnZ0-qZ-NQngWUs zs-@gS_%e`Z?cIbh=nyiR{P4|fepq?6hy@?8lOo>!9J4Iqrr>Ak8W}Px3%}CKQ$-bV zrBDTk&O~lP`nh^5wRwz?H|HP!5Ul(Bnk;|f`lI!*43PueKr=@RI>~hqUva_IVDm!+ zsPJ}cTVDh&YtY@np~L5SR_-Si4ek-_garp4CVZYiBF(QTf=@x@RpW;T(?|ncgJjDP z7F1@~8KdT9>WLel24%2Kx9x^WV8SVdvO=Lj$6e{1O@_FkAeeF3#c&2@A%7Y<RK6Q4 zVEkg+DA)$)#wrL8f@B3p1@mwH>{gsJyU4WJbBAX$^R&vYyT4l1G}7*7cdce|5wMh5 zc@CNWC-+%63gxs(;Fu3>iRGyoBh_flIfP#rMKCj4mZ0L_kv=o4$<kmo^ZolWi_6pe zIhXHW){Mc4G&yLeW%Xi8NfGs4%1lX@?`qrp;>>KCs`XWR!IV?e>NRstW6>#^C8BhS zI;ZSL-()og4DUc{&}eWz0oK2|-E4w6%aGc1ct|Iu%Cs4E5*s8Gafe8F@Zc;FiIDYt zD=sV<jel49XCs1c<X6tP6fDW_AvDBYr_)<Vr3(2mS;M$+zZbC<XHN#tZKD>VxRUPE zNNK0vXRtP+FlH1_zO@&zf)t1;xSOf+aFEe#wK=1HQ;N=A&nP+w`X(&YsR&pBW%Lmr zCMh7zd&kPDo0Q2hZNS&oVx9(WJmyWOK-z|Kq2?GKc7usZmxn9hJXn{;F=RBb9v%FV z-*%N%TvL{Uo7L&>Lwy)`XzV)MvRWDqX^Dewo97N>I$l>y-=iH2Fg!2GXLJn{lDbfo z+P2Kr9(vCzU$lKH?Vh6JlziQKEqOZ5S)ZXZ!S4{{uBu%idj0mNPQgPKiFcWG@!#;@ zr#MU63HBU4JoC)JdR;S5GtwW1b3pl~{oP<Ysx|Gl@7u_+h0-Nux=Oi;MMImq_rnfh zAQC!{x2>2qhNKSD9VI2MO_?+W>w-Z+kA_em<<_AEJ@hK#7f?sF9L3d61S{WSuxeJp z*_!QOoIOp_<UM*}C*@vNW3L5{NNj{T70JVZ^>n|TTaP^$pi-k<fVp-%UDD>zp_L}W zmu|JqPB|@1@<Z7dB`VK2kv_ADFyiRHGLVh3@W3U_6JoOFWDJqOp?r3-uVK)iNM+<! z*^+4Rs)eaId#}p_{zst4eTF+Is_JM9nlcjZ7L01byZG?X4gX*{EC*g6q|%q!eL8}q zgq%+XjZyvLFCi5biLG>NrUG|u*o^_dAE`*5x~kTBwvOCL#yC9N6&^JSouO}?ofr`T z$D7U=&3xv#auRK*Y*nhCuTT=%MyIuIgtM4?n!}n)nCZITFSbUH6<|&2jw%<JaXQxD zY=5szBx3T1RZN1xt8JrEpJh@J+sdqVt*9jm`LTT+sjnF!SWOg(aL~Sd&~y)N9=K@a zWW%G=e9x+|_e5>whpMWDi!n*C&;@qykT<5sQS2D~S1T4WCz1w~{8}Goq&x{M=e%?O ztYGGAnyf|aw^z5Zu)*#Y(mi9aFteP97Fm5Q)$mir{KCJOKfS_TGOI=eY#J;gRTM8g z!{UElbQsE~w}W{h*u1J70zLh*D&i?M)aa1Z+b$c2D-F#ep)L`bRs~FGaG+6E%}0=^ z4bGvSqFyen{kjW01|NaxhPLq<i3eV!t7Ba2#n~R#USW=%U$Ng1Re>5F#Ro?V2IVTv zfH;7fS9hK$2yk1iU|jlBuvG`bwcLuY33arydo?(WU4@c;2Q{ahcnI-vYchKv>rhbe zmL<q2yE@j7&4!>_w09ji{87!54i+h4(UQdwJh>dA)4Iv74!`MeVg8j)!KrpU@jZiX z=s6&xr7t<)N`pO*>j@X2sBMGfb<?h#*PsZQ3EnR)Pn-SX=CB?Pf2Y2(HpR~gt6<My zXGaIJ+EZxCylEqXWU4*Dh>GA)hdSb^ap~uSngD-t02w(flOU9aDe8}tl9N$93yRGA zSU5NZi$-n~N}C4(Oa{MezM)V2TzeBngjC`<B-6Xr?%t1J8^W|JJpy1#Uzjt)j~H4E zr8y8R+1&YRiYO;-{CNI+l~CTPIYV76-IMxdhj(&wcsGMKEX(Bx^t5hcOp^W|y52E7 z)23M)or!JRwr$(CZQHhOb7I?0Cbn%%jGcMj?_1B>Ywz_l-PP4+RdtT5@3XtB5Qt^& z#&$%>{+O61&OT{S6S)%*r4c7&u)Xy1=PPjk%9rXnA5>%syhP@lh~y`W#^Z+unk}6m z7w<f<XTniZS(L<~@(pYDg9}C>GJ2;eci}r5z4zc@lO%{K78(ie<pDVEm^#xJ?_^@G zGt!Cu0Rmw<v@^(@B@jrqd<-8p`<U2+>8_Jz6~wh*Acy3q8dB)Em!9og+>5VKc31{W zLuOF}&v1*?jZU#6bGc@>P?Mgg0+dfr|Ll(6936KY94RttC_S2kO0zVa<~FxyUe*Dq zw$f~z-4_7n%?A7ky*ns(0izN_%zWmbLw~RgrQ^032#z2Mq;2eReG_nBwqw3%Q433x zQ6OuE1o-pU*;>syNn<@ty1H(d35z6jhJ53~&Vp3{5hMbX&wfI-y;gs<>mQ7~X;3U^ z`Orc5ie@sleJ3O}#p^ZwH&^V1S3AMnSRo=i6l47Zfll@>x!tma*QWilKPQTsc3IUh z3!MwQX=NIBovm|Vmf}(MAPy}0qj?xNC7loWOjX4*kXX^Ps@OX@*i_ea`ASe?uGH>4 zJ7yC06M#p!e7Ue38HLxoAv3BLBIZZoRQK4kQ!Fg!Q8rURm@DE@SRPf0Hhf}3ALLj> zmr~WpJ*MJq!E$?o99i_DP2qY#Z&vY+_0YjT4U~kG(2l*;KDF5R^@1NT5I#ro_u$5X z4{wQm8d1+M?3FU}XHt3)A~5q~6_gO=USj2=hTHHVFZ6TzbVhY>*Xm3PibzK9^R*cd zm<P0R*LI6ovFu_6ubf+BIg(^K<4V-_N>~JQVXu}Je?RBhQk!)Fj;~lWPLmaOU__?i z`!@t(Ct6aqXjruEhI4Nu-u#5TGZIS_7QhvY+NbO64e$jd7HO0Va$8%?l=Nfb&w*9V zz=u7R$qIuC8YWj23t@ZAzcak2c?@=02c_)waq`SRgH~Jo5Z4)Ks3NA~<4F+_qco7E zlQ~v{^ZEUX*Q){w=rKiw15@f{7jz7m98zY9$MMK7A!xDw!<mQAJgl9haVZpER#*oZ z(**Q*BV{@R@`_jH>B#s0H}CIK1B7V@@{b?8vHv?^!u}t^<hy(|3&N)l;}h<}#^jic zHpY)_PkS4zb$c$vj<yx)L|N8~fx(PK;n<?0sgGArK7<1S4TbtBva3ZKQW+uyH3Qw8 z%^`!AgWMk7SdZL>F{iZHr`O+zA37}EliRQ(xKFRSCb&LguU)=8ALqI`SuiCHRt|Bo zrmJo&m%GikPPM+wnVave9h_aA<29g)*B)-{W2G;l>8|Oz*GITKP_zx3GlEdvl|f1! z%YnPCx?6AiM!Q$<B$$R9urCK5beSG62Zqdp`aUnGe>oHQOPTsPJrIXd)qzZN`nB%^ zn?B>_<(_n-d8<42b6#%;ALiDvlEv$-v0WYBi>GOj<<%L_57yf0*RI6VzS2{wo*JIS z!<NZZwPc^`y$R@H-kgJ6o7b+?0ecsoH->&vwd~`8xw4mK;m5bC&*gbi&{eYq&h_&i zjF|05PumBGQh5Xj-am%;dqgm=+;UDHYuimUKB`((T=63O1YXD_=18JEj<>V}PYsoO z7Q4XRv<C4)@lZw=lvj7Ct2g_LRqZ?q<`qQ{^F(s_QidkasV~OT9XCl)0=!kF@<$<C z_gy~xqlUY|5Ax&d6Q%Guw1G?rQ&kU-3BO+6o<?v*)y1i?A79Me6l+S;zFIRPecm}T zVZJi6kFL|>V|WB>V0^K$qe4=@%1M69-p^MSHbfS-p!Q^CSzFKd(fH(6x8iB2lDwFm zbxr%ewP;}#7CnIOit7f2gPr4!F6eA`uY0n(@)~>YRpP<)6NMY_CLruZyvgR+nyYhR z$<#|$Q+pbedgTm6qH3b5rhhtgJf95Bjon5<-lID%KE4B}2^7&4=PbV8G)S6kFq^gr zGsMN;872#m5mFf_Nx_oT?S;))ha08hsNVh#qb=i8nRvfA*t?XiZGlMNnDgyk--_qt z9labm91?11I%FFaeF+j<8;UHPFWk?4(#5qw_VsS~ayXaw-Yvw|r|tPXTGRv4gpzzU zsf7)Mwn^j<8ltw&&N)uJ*nj5sb!R;OJS29?$FtQ};fBg?N}^||Y&7BS@%X~*@&b>4 zO^+cBd=i}WWx{#q7}p5%?*6)aAI%U)Y(A={1=Xz|Gk5v0G+E(iPyYUbF+8amNqWvS zz$=ZjkRBH&m@C2#VW)lpK=J?aL|roNN^Hq6k8Y~HNFxpMqlZzt7~#h%)<`b*X1b#S zqH@6sHb(>HPkLUUf#ZbCg9>CyF)z5}2dCGNv*?4-dq|v>H8hJZ2<$bWD?!d>NKqaz zAXYKDeB_<F+n)?0ec4+i>ggu$D~(%g=mcHu{A9u~18}KRtEMDLNA6(8NdIFFH#*E+ z&xbN{eVjmMA`XB{0Hm2VD4!(4kYB}$kW_NRnS=|*<@IbDkDIGr<$M@QQ!!WiR?F`{ z@Ru(Mp^hq!bFHWANUjsH(^I#;tkREe91B6G&2AQE>?)aUY;%9+^6G5*y6`&3$}!oc zv$Ex7`J8xqjSS44K1D4aRL6UMypc4vw!O~pvApW)^87^6Q(f^D0{D?iy>9;THnPea zH3!~;TY0_sbR)4#(S_Dc`#zI>AGv(|Rw}A_O#7&dZT__2mcPy!i}S4|=*Fkar0YCu z#<SFv3F9<b@%K6`$o~EuDM%d{UDveD-Km`CB=;o&Ywuv>)S@0kYY?Lf_%v5*4^|X! z4i<`JQnW7{`+ms$JeRkFyP`Wthb(;VV+?4+MN1zl&{oIcQ{+!-txX0h>h5+;wKeRu zY+d8kNywhcba7PVxwyVXnx71YOc(iqNQ9|5LY7FC#FJ49-+7CI9Cj?CXo6S`m6Fr~ zSL%kUoi|}<+EW5`K`^x8dd9R{HAj$#IFfa|&^=dR-mwd#9{s<V{DJyX_NjWNO%iFv zIHq1cP1|&06->%GvJNwTZX6%0zSv$I@Bd^_1XHdG8dd+Sl^wOjk^#ZZq^(RlVEW3^ zJ|LE^a?K8s&kG<*utz9Sfz!X>!wwE35=!GIS{j6qI>@JO(DME$3xS(1O%}5$8;qT2 z3J@9Ns4Twp&mVMaU`XaqXwM6BM9Y@j14k!xQj8Y^fPWbcv;Qn2RU#-<>Yb4YkR!^j z9By@SnzV>dQ+|PH42rZls%RVp6IUdn*%!iRfClu?wWci=AV6}**%sYc>!O+?AkQI0 zX(wU+^e8H^PkZ&p#ghhE3_B;H1$T)L5US_HFNOn^heLgxuP6W)r9c=J2jiBGm*-PR zcbG0SgSSq3Jrz|M?rVkZXD&ZgB~&H|$Bo1b!4iTKoFG{&un@Pn9juO|SdEe^6CnN& z!$VFIDrXK?S2i~eWJ7l4KU<xt&CSNdgqG|<Lt&rPArBrz5kfxV>0P@s>19RX!xWdy zc*HDX?4X^Ah^6x5Q0uI4ny;>miKvVy-A>@-_AMnH|KiS1w8ePT>qkt!>h&*i5x)dG z@bd&v=Z@s(p?N3=9{}aF>td2YnNM=3@7hR{^9}z}$+S9bYrj!-_;C@};xCq{)Q1EV zcLFC*n3x_VU}XR-@e@GlR9Vf!aDo7I9w1!Sj8tzhc0f$MVZo#D1L#D#>P4|P3?Aj1 zl!UTaA1idy%98_LNZGE#r`JNF<$}M(vS4qVtPPSIA`VDt+z&(^v@0HKY&{z+p#Nj- z=p2E+Kc}ZgY86drg$&#eYe;^SSJAuyhgr`7*_}!q5H%9$t*I-tk|7X*4I_>Aiy=(u z5&<1*_p$#cDDMCmm|nC3Vx~q)s6elYk(@^C&oPN#$yA8A6)~;&3<zY=)l~H}uM7kw z1UkY$RMW-Z7?S0$GR$;dtx_Cv_Nx<r4#zni5>Sw6<Taqz#B&q%qOHWEbn7OZ^!qc> zX$d~_ry!;^2V|=mn;}vDB6PhEc1+BlAg9U^SOt8XGFXx<z{L5@FdriB3nm@N!HhCM zo1UK?Wx@(7l6ri|fEtMHF%;k7K0E~Jv-gHjLM;PKp&V4?<Hvsz1d(&Ni=N}Zd7kC~ z_>eircu~LPBLYPU+7Rm2?P3>|vnpFj8+bjB_NcsIlbrtWAe6gXQja+0m{ekx#~XRo zLl`20ze59V055F4r4O}#P}pDYm)397>=_ynS+QXiUvoTaqw-LJ;QxENzhK~S9;Ydu zg3#m9=T~>tZMSk#6@f?PBv4QtOO7VV83Ol^aE{G$o5}1dzNZ#Zu-VV>6gj-PfWe=T z%HTs3OBdDXv$krFNA8ym`r;DN@hI|yS;AV%LUR*wmi!fI>3E)%QVZmsT1DXxYzg~5 z6vRG|#>Es+G0Dyx!>jpMZGCmj=oVu*O|yB1a{?Xsi|!FJ_H&31;jUXAd|*dxx8*Bk zz2N0F^J5W?N$2&OZD%_sxI++vQBDxYgZ4PP?St&wlJZ%e`yzPR^Jk8q(U2PDyf$}G zkm_(24qh<nJv_wH(!)0LK>X()b#uE_7GFBaqGCoQ$+7R|azvx#wNmDReK_m1g)52< zO;Q1#@GkIj4jCl^qeQoYHe!SzqlgP_*v|}3lWlw8{!#%+D_#I~)Zedo)PMRzD_nr~ zYa)=>4<(gkpqzg~qsme(FtH{tXfQr=SbR)TLlinY_X_@Dq1Z=->{&g#93mhejh3XG z#h)*}8j_4G5{jCF)$PZRQce;G9K{JkFGR5`N5*yr(1&U&Y*#UJEAH-7h}0{q5BVkK zClu`;p$B3F_bP5tz7C)akpk+xuhAC28nSF<09lHXXPJKnyjBHFR$)H<XRekt@cSe* zSmg%q7fPR*?^w>uJvyw(UMB9H8WZBIKx$Kx<djhTf|S@rf&nNn=~n_CUxm}06w&sg zhF|tHoM;}A99LL5l>#GvfY=}zDPIU~xqy<hy&%R+U)B+Y?>s(r1HM3BN52dug5*kQ zi=~T12mdIMP{gkRXSnf^Pb>s~p2yB#vU;mxg89WSF1y8YVsIj06Y@sEy|{C5b#;FV z)B)eYvqF1L0ZXszP|7k1Nn0>Hjj(@sdcRvidU8u#kfGbGo6h1yu$k{T$40W1Z>V|* zDz`!0o+^uQJ{VtulaTZ*m}`%>QC}NwwzV;BWH@?eFI8%U2b?S=>oHh6F|RLg^leBT zMNNBaDPv1sU)BfP(s+yFv{1H+bgg*<N@bOUss*j^_S2O2pMIB)fs`xyk;LY#{gYHV zBEU4+l)7#zX-A7?dIH?&sye6(y?(E*6yeU$KQlVbg+p3x#b}sG<Ak}aCcM~at%hX> z$ys?tnXf5NuC7tQO)wwW%`t4X$qkx0$q#gJ!{fm(?f$s*>tqC{Y|gA@>w%&WyG3M@ zW?w<3Ytp8ci5WEQT>5g#8hE9-k|5-VnDcDVQTVlAs<w+`aa#|$PQl=izTasm8(P4q z7%&*(u|*D42wG+AP2Ec!o?q6+8^un!+(7o(5fS_}vK@Ib3B+TyTVlV|*cwEvyfd_) zpm`V)gHSfuqTv082ze(59*td240~v{ydM)I1v0V)4?me^v=Q|NmJ0>3b1Y7lgns}Y zLaEZ?f*K%~-Z7vH%_EL`?{xbI=nzy*9LQl#b@QR6;jxC|IU`nei6!xKJD}5+^O<c# z7MX`@SXS9M=`hck2(fz0LBB3R1Rm^cc`SzKqXiG2>Eh*eOh+ed_*x0^S_Nj0G->KS z@1D8m8H3N|FGuS40w9dsX2w0^f$63aAHQjXiQ&<vhxWuqV`3zYT$&W2(=BkAIwk7O zBPM51M{Ry^kJs`W`#oIFg!0^wpADqwtm|1tXFo)r$Z$|O6oJFT!sjRfh#^kWBMP)g z#H>aI+~w~L@x90oOvKJQ^7B$H1kqg)DE5;l2!9EO^Y}7A%kAP<^Gn~@2uHiDn2TnI z|49=OyfmqTl|L3-c=<wg_TX6n8;?<`VS@;xwyDSxiH&F|A<Gn+Lq;%Na8P=S!?6;9 zBdiU=#@7ZL!)+Rhk6you+`>I$6LJy4X314J=oY|o6LAk$u943y05fs)xvf%ck@7pw zZzuYpC`W~wH!kh^WQ_rWwlxfnk$Ey*4(!OjB|t@_VYsiwFzqZTaYDxIy<HR?M4kF7 zrliyo)634k@RfGl?>c%=<A7G_EX6Ib9+#b^zvnBPe>+q?J3*F_9F;ckI|6;Fd&DHh ze7H>exAS5!)F}<N`2g6%T5*)xDU{Q>n_ckAE(dyVHW$A-(s)0BlLlDbI&_0b@!ClU zPh-uSt9fOr+NbnXjAtdLnOvlbI`XBy<C=Q@GCNMYTajFDS+!b|HWi<~!U_$=g<IK$ zEp|;J@dh|1{K=X71FbkgFL;Iv{St)ekdwWpFt=&-0(Dh6dFN(Sf|!%y+{gr_U{P$& zdTih|1z>X&`n{lYG}8=d2G-Fu0=q#VV5lFb<b!p216=Ak$IvJhWzovgKu!+Niobz$ z2dgr4Ilc@*fO{sgg8!!k$B^a+A5me!Sc1(4(0*oBP=0lwnxPXo$uhC!P?NAOeSVU_ z3k8RH-dK{c=-H5PB4Ssoih0JAw#`0}XookJI#iu(dI31hG?N1h1aM>1Zqr#f<w71S zdH~C`xg8?2x|xiz4}=~~hRxz*lOH#m^h8HWV^j$HbKg;F4hHdoI!3v#<{{ioKWf&S zm|P`VK`6h)B?4xvh%e7(Z`Yl`Nd%4hGYt98##s%LxOn7YfN~-syenkiM%B`MuwuLE zPqyy)`L0qlbafD!3%zPXX9<8~j@q_YvPRw21D8?2V@VDOKvmI6X^2o0tk;MngrBzX z$F&x+x~zQUyZM?VCurD$P7wgd^fq3*l6__po|=|0C;nTWTDw=fCl)~@@d(f|@T#lC z=EGG52%|wRbk$Mn!nZU(;K4yQVP{AB;9+*SHWN&822l1K{3z|+Ko3XhKH=9s2rQ>| zsHb#qroiC;GQNU4+<I<(px8q{et}x$&orl}_3CJrA?7TUVW=eSq<`8Jv)EP(qS(Ui zi(VW+f_`6yp`8um5D8{Z5D(|knR4u}L0N$!jRP3s)Q!6LTa<ZUJK2>Z-MTN~g;c%s z)f^GnZVt}pSk7_ewYx&b&XTW++2TGCH?r17%O-Y*1e#E)iDq^1btYI)pLJ+De+)g@ z6WjX?;oNKmJjlR>^jqXmA|+*01>y&Zc`vYozs~M=!LiJ?lL=_;#Uh;*QGDv*ztR$N zJ4`}WTv!6Xv_I|2oC9w(C&`zx_KzrWUot$cRqCrUhBC)g(Bx^Ca~gE-p4IceAHk{v zRxWS<0Ejq5(rD3`v|ibx4Zt9dz1PsvuK~PkeUGKQTXF(FIkN}PvlZTgkim7<yK0&b zU6wv!CG4#gEEwcSF}?xh2C!dS&BJfvx~}lk!XHB<?8Cw@4_^XBfml5A2Q+@wq0A|O zDxowX@FtGS0|7W`xz6Y2lEi86g*WU=0G+rlr1}vZG)E0keI2&1mRO1%M{}n(13E=C ztHmuaBpl`lwB(q%IFRs*sZEVxiO|nqpaG33Mg$W>sO>=?S{i?ih4oEeH<}BDBs(s& z3(X3mmr_vI=pnQjrTd7iAXp~G!Os86(F~R#J_XkM!cWt4+2qej4)|jP9uo-&>+4Ps z7Ya>Q+_8|hdTT4|aFB4Mt>7kv9>4X3HwdJDmk40TewID|6xy972(8*5IeZNW2&LrH zh5>5m@*Q($3tCXrniELo2>%~y&eqGPiY!aTbm!G(BI*{iQUygI9}#~t+2Zteq9t!( zPB3g%7ajSqL9t@W1oyx?npf;ZJip0amrJ|%4oD03jaldfpskI7t37-Ne5>hg(ZH`^ zW&LHEUY%H*^?>D%Grs6z81Dj(F+=+Sa7*C`Yv=<jFI|4-9qGDny7!_e78r)~ftj{d z^duxMkQDYh{dA886srbFHNO)m2NvM?&*^^N0JgU)tJj7KW3$N#WDJX0x+)*5h1C^f zdmA{5T>={WgA(Z`L1P`*GZIXKi%BhVPlv3BqgW`3qe3PavE4KZJ84Duu<THbSb%Go zvN3NKI}%t`oQ_~WF1t`?OP;Z(S2*sJKsKW2BDA;Crv;gdKyK1#mccRakO|XkNaM-? zL*V8axt&?*hLAYT=>F_9w7X5~L_W4gS8<v1qB?1o#xKgaXcs)`x<P4YL0nMRr^VlP zv#zve+CPoUFg8t-=9HtA$K=9L!_-KYM@e+dehoSdAa-<+_Wg(T9m@BKCSKG3)r(-5 z3h-TpV9&mHFNh09bt5j+iBy9CFn{h4Vl0k@wS)7=q27=WvtRT_NSoV)v8y=RR#k)+ z!S9IIE>nO-Tdnrrshj*il)tH78x+`0@@uSJ`@Fp{4$wJzQ1Y0CrJv4Nd29vK!ScIw z2b%HAy&>#<ZFWTs`+@U)PASqbcehlSGbOTL|G#g-<KW2wm@vQDUX`CeF#eY};q-L> zp?q~6*PH8oucdkd&txb|2`AoZ%cC7dQj9beHRXwi>~lDxfHb-C#jqnH&bQihk2?Px z`h<Jcb^H*AkHcH1I}JjrmPw=sq(X_@ruoGyu(ka4YH5<nLujktHZ8ASBXNk}Fnl%W zU=uSg`SqHoL-TPy^|am9O^3>Jr%|&{IHH@Bm)XEZA?xk?dA_@Az$MJ+D4(rvIHTRV z?Bjef4YPS}X&D8>tBLn67jJZEXQCm)e)3k?GPj;h2zsE?u{HUCs=V9*F5I|z>i{wk z05`_=5NG|GcV%EPg(|dG8`;J#B6`>siZc2ZHEyc|TY7{=-QOd*`6#bWn=0b!Xy$y@ zHsRjpWW#QDvb}mUNR}!?wI1V(k>B&aC~K?J5@((2A$r(-sFf_4Dr#I_!Ahi^de}}S z8DT*t(e`jv=4CI>W2E8h=5_wGG?<%k<e*$@?kt|%6oQW~QOFO;Ex0tb06RpXG0PW^ z+$F!P)V|xsS+Y>QXqiD&dk{b^6Eki!s;ftn`9UybEL@BvZ}gjXR=>+|zO4>k{vuB$ zxUaEYUbVQrqTIRI5pVf~U&hK3N5kR6o{izd%@1X1`=mb`Xpi`Hn<Mup>Se6UMDuVi zKm#21_9JJP{ISJD_xif8uqzwub)2ie32wY-M&c58ixWVZ<{HW7!{vyt;b5G}Bthc_ zdI;f5Xs-1a>j?GF*FJ8a?pt3%fQ~MgV+-91q3@Rup@q|V%bh0|Jrx%p?V!F&PfHID zHtp5~cWaz%=s*Tp<kfH><R{@aS>Wc+`zvXqV#Ly-rrD9xryWOmUEAEN*Dzx<)@ar% z=**&l)8fY!yep)#w3*+}QKxRvN@g9bmldCMs+lpfaIPn!J%r;ikcG>;0%qEAMsig& z%*tYhcYgjT7qytXjBc&~2)tV;U!<%rQ91}|u9>pqXfv*2z|U2~WzYo!Lwrh^bgd0I zE+u1_jDDY)hT%F^oM;@mo(YRL_Bbl+c_(#Op1lUw0mj}R#ZxA3Qc<*E7OZ7O>QYG^ zXd>majh%iJRSYsjZP36A2*=1E5^twCb%2(XYwVT7+pGG%vlSQ&ZlqL@zZ_f_FPw1h zELgpq9AZNDCnU&_d<Rd-lUuC!KF`w#BJ3o=;3Pt)c6S;-f*_iGbDwS?mch-*&Ts$f z<m^%}7bpYb^M0T*LW@04_X7beZ|?!U{+Q6!CVI?4S0j@5EwB=H@z&{aAhum$8SMvD zeDp96FUl_VAvbm|G4LMl2Y!c;ZzIT5>$VPXfUobM+IuXq5KBF}c1Dh{3r|6rzdbvU zu^uH+W+LcY>G5{o5v!f;Z6f5nx=yB{?<s#(<WsTaypBjbl4d0C`)dwo=9>{w0Zq?h z+Y9_^(Ome;T*EMlknngTTUiDy1bTbU7iT-60mqlRh9iO&IejJBWYA|#pezdRfOP0f zspNtBvOnLE`}%|QuSZfQ{J=ha)ARS4sU|n6Q^2{O?1+pjd~O0T`oJm^KT@)&Mtp3= zAG3~iK+%pScylx}@_u8%T6Ix@Rbh4Dl{`VK47}aVFjqlk)yzdkR@I8_5Lh>&c`jjz zHk!t&E~pic-3_Vk?bGg4P7MKgJDa|&W_3hvQ-S7QN>50h&>BHBP8@o2qW5o^tm*o) zs`K5{>!bv1t?rUxW{u={Dqc?>vCv_i9~0c}FUGv|-~O(rSN?O+F?5^y^|}=KvfO;> ze?FbF`5-tSLdfI%O2c)wZU#fD7BGAa!}V*`3j~HW{pEVB-k}@WFrm#jGZJ_i(vQm3 z&gXOWuIF7);cM57FH?bOgrvxYPKUr(tQ8LmKcQSH6D>?H;ux)(_6xW<Z(lb47*CJb zuTIvGf7Ffk96sS#>Ty=^v-;foES>RPhQuTKjtfX>CR_hE;O`4yhSbK)a9gzet;RSn z(F&U$_VnI$dY`;Kd9^u%@ZmBNbdcnB?==rvNjV%EUV#Pw<+9wIO=P{eFDm%Qkl+q2 ztj<;-S{7zf#);GZb1PW3yJJ|{%6N%Hzb(4k=cWFq03?Pvkb1{kiagehbDmBNx6d?S z%@NVq=ZUPSr+O5o3Hv!K@5?E`5U*A4v_zzChhI)qUp6P6x`8)l(o?iw$8foRm0Wa< zZG+;Ik*2db@FCh~%KZ(tmkVmNrP^K<M$L}{H{NXb2JhG#;DOHm!2`5Ip@wem6WS#^ z4u~5*Cz-4#q0OdS0fq5Lp>u-owEjQF;jeh{8Fip&tMGM)T`!+8%o+p1N3A)!|I7~V zC<rwyZ;dRKB{V<RCnyD18X}*eZ<ti<XCmAZ9x(*?$snosNq*#TEWRmvsk;{dht_a& z?bD2ZVdaWtR5W9PR=Y@Z&vJ~s@@rd*fjSGS#&X)h!`1A5(CQ<~Z1+LekP_}=ZT40J zF!0t8^egUr2ntESgBj$aVJDZH7GEK2*%l4Q(r`(HRE?m5L6?h&73~<z;WwnsE#mlh z1X@q*Yk>xp=2NYey$f}=J!~ohrApG)y{^|fbD{KNRgPj+a((I;gH+uC897YmS*7XU z_fs_c+I1xDLNMwD8jY?T$#);G>8033`uBVu_zTk`Y1^PVYb#({?C{PZYpv3yLDt6? zH;TBmu%4i6)j(cvGu-Y}c%ugNjH83-u7e<>F7h!$fZ^`>JUyS|bE1SnVn?S8dap5L z&!4oT2Lg}758MQHCV->T-&h{KAbo|iLq&AxxhQbOOB3~Ln+R7f?@AaHn)E7<g~$l- z8yR~c$HEZ%C&Lz<4GUX9CC*NQMy(}fw+Ygxm{dK+6{qJoBr}yxdJZvPgI>)yM#WVB zDp6^*%3B$BD9h@_Gml2=>pPq_qlcn7$ZbZu=?b3mN1Ix8_Wki7HN!;{++2sGpzKxY zA#t-C)4!6)O?mMuae_&Suc{o@SfP}77J_eiGZg05l2mcfePa@pP_Y-;ha092nT>9~ zyxhN{23a2AY9JmVKS34Jzec)pk7wxd;A&8|8NdnGfOa6@`mknv>>9Z?Y%sd`e6EzB z0@;%WXN}sluoS=x(|~lLZngYT&|Qa=ei!MHAE0DtNdN5iLE<w<wxBS|g%gB1QaJS7 zjtO!iN({!?V>p`0?i`{8*hslxaFcLN^&(Q|VEas9CRGorWRc=ksJ7ZNDcRF-fH@@5 zXRHI0ldkkvv^)NmNQ*`V1?ECA<tf_WaMxiN(SEj8E}vq)yj818OKZ%H2IducjCF^V z-`H*Y$0bA)8v4x8Zc8Du1v^7_B<0Zf&@T0FkG%1P3Fz-}-SMYdUeVp#f1Z3Kh8_QE z`rD#Ku0m5|#Dxm_0da_#K0TCdaJXmjPq$ciz6l2^=>Ji>_}y-|?aTc!G3$BTWJ`GA z@!kE;FyxBw$ts6DXlwrS_djZOCjx^tqN0xXEb1KWndjTs%v6cCXBl!L1AT%uVx*4u ztjH6yzxw@BZndx;D3ffBI8aS1OE0@^zo9HgBu83)L*efb0Iv8}q4BMP`X3c!+oNtY zHOQ2x%imK5x(MGvbMLpYov4-_jyloQpq_WN=KQN~Inr5Fd*&Y>jkA8spMU$~&$qff z-Fz>?`L<S_Zx{ZL?&yEI<GlvH)xoSGThM<4#{3=@{S7!z`+L}aTdTaER+gr*AzO$* z_&&K;HlXV2ibC;GPCu=_aI~ie6D}2w<GWrYin+aR_%4e%zsxW{Fz}UCrX0(fGs>=2 zzSW|KbUfq%Rd`YED{p4Qi){|)Qx)wOUvx9@WvxG!#ARQHPQ#|p`ug^{v&i)}j1MjJ zOvZ;yIj`ILHI&HawB0o_bsn?uzO0#m_(>aa#g)p#+xYrYf6(4IEGrhC<K0r-HBQos zbDl;DFaxfYdz1~;@q%OOJ!odmitE*HTWRVyeQD7nXmYJ6Nr+9r%>Cl)j%n~3{8zpS ze5BNOY+%M?uuf1Ix#$_j5xEK{$al?}OAn)GT;e<Yy??f4#`j*nwn3kF;+b9JI}dKU z6}`T-qa3xc5t`lZ+&Q5*F3(4A*WF)NfAnwC7D_bu?3uNmIv1>QlQ9nwYDR#J7jFt3 zPB$E8XL}lU!E2NaS6$u_Q|eyHKUGw=9SSfMZ)<l`mDCF(TYdqX<w>4QKhW;05`)`7 z4<&1^@EoWzAtK&RAVZgey(oW#?x?aeg7)XZN%>K?D@EapJJbC@5)bDHB{s~%0{^3L zl-;o}D*cQ~W-R!oKYI<|pmV+AWTQeOWPL!|%zT`*(H}d&e=-MZrvDL5vmt&El|<Be za2@Z=_(u>@JVK~KZO4N1K+vM_`^KF@>2*fnqmNN-D=nha&3^YO;8q}qAir^UUgcyX zR;#3Gz`6dgb5DQ?!hDoaSjll)3oO$GTjB`Q00e?BhC7LnITu@DA<@TRR4n$-0Frk5 zfM19U_7F_WEE+hz9$FnE>1J%%8@~;|*zj@dG3<kWd}X(`C`HL3wIO`s3Pq6pR&<0< z<4#ZHdsCdrQX9SHoPKRaP`fO0=eeLu;bhImB<l%X-ut_wjstheCLgv}LqS;fweoI~ zlYsV!*g(JQ>bM*3D?T>x3@Iz>tT;A^y3K(tr9@)942?m*^qsutO<8(IRB9;l4%!^J z^@o7y(r(S28--TdkXkOL(EKSEn91U_i&17yf)1gi55eQ~5LSSqX$%RGGgOCBpt6wo zE^T0<r4P|~z5<uw1zF+B(q&&nLK|<q7{s85#r%hki!q-)!n0B3fl1|fz*Y3{k)1l( z1~~`*G+Mlm`Ii_RR7dbS5Y3sOcbXe677^@?4)G#JNCjF`$WmM*A5<AY$8>J?{k@Iv z$_wI#+royft(H8+g-WSPnuKWxk?N?8D)@tbC%GL_x^{9%f6Tgh(|7K=a4*4{W~2gu zhKm)*g2ki5vGyv8KN%#!#pI{T<t$L(6>oGVo6W<MX<B90N5Og<q|np60z#so7?MBt zR)m4*=H&LURN26kh|xPVp*D~K2xakL`qC=mz!t7VcaAc5ZBbC#%nc7DGx@m$-=OSk zfiJAt!nZffaxPSqv}t-dy>bI#4wOMopu+3#7_=iQ9VMiCVLG86%i5b!<esNPKz&D7 z(Xatb_ocW~>VNAqRsi>5<cVGW0{Mh~(xNt^VD)F{wo`+X0Du**5GD1u%YaVkr2`$I z_e?%1P6QGG7c>GQQMjGwFa9aa2M$s&U70sx)d9`1hw#$pj^m<JEpsOa2*bj5rVy@^ z2kPhrRs>4Q!ZCP`#w2B`Qzpwz3k8dvcW<#4HMiG!^Zk4AL$MOm85x09x|%W@@VV<| z0iR*WXs%Om3pm3Yh+_X|bJ5|nN!*V(6J9}ehY{+jN#KK^RvY#@)0X8ct)J=eAKr6V zF&u$9_XQ1K74=W&IPdbtt5t6fKpZ{pUM9jVP-imtcd}}mveff9J-m>=gfUy7o4&6r zXT~Y$sP$*?qdbzb45Sc4Q-F-%?j6G#t)!vp#kiRtPqW3t%dU%g2owwzpQ@9TaEC{W zxfLmpmCYRE^+$wYp_MW*UuV;1!f@$sI$&Y1G*SQ{DGz&j5>`~aJc?`=jj|s<gDV~a z_1;)bjd?YL;8$F|J*u0|`^2*O&e;`B>qgl!x_`Q0K5U2<eLOkKMg#9Cp~gO{mIRS7 zPJm%-5H!DEEyCW85fPD{XvL+zI4VWL3_ARYsDyJi(b}d1d`%OFG6!Z!oO_d?YS3~r zg1I`W8^;~>y!kVLj*M7Xk|}zc?T^_q%7IFMQT($$)QXa1`uYoK(7q{BMR*MND5(PP z$c%LZe$w7@%n^QID+*3Diz14x#s+?C;e8jH3J+!fq^yI`ZN1*w4t~TAWRB+USy~wp zWCP`pVAT925Nm}G%7N*&gD@_!k^+lcshngHT5t((bwpH@X3vR38Cf)oCW@`w27c$^ z{U?kR9tc~8kq^=iSqTZJJ3z}8IjX@0tJx?E#ciUPkE?!|HTe%j{mD_>VJP{ix$TN^ z%Hijp(ez08kg2EwG}gC0SJV(R-4-zYrg!`*k_fujNCS7_l@z|dA+R@X(p@qY)R2{L zU{Q<rq4dA}S7gL}zb@73hA*nf<;~t_dxwzjEhM<Cn1kqtmZ<}j2z}!K5X1N*K4Eli z@S2E`?C6Obbh!{=l<JA1DyYwde*uKQ5SsQu81w>kjJW1F22%O(?Es$N0==bt_V?~t zw)89Gi2JIesXjnwZ0uM_itPXfvS~c9D0^Wh`7fgQqz+2!s*aB1H^o-tq16SAc^|bt zgrTNLz18}5Ca?o_gWcot2KSCE*AoddBMJYuko!Ppw58NKjO!l}`F;pZ7e2FdfVnF{ zW?=9BI&u$vrx^D7MyLXl?qLPYZv^i6N1YGl3u#~*!Wjpf_D5Ce&Fzy&6&Au>Jslw} zFDLQL4qb7!9GL+z(XO#xo+6LoVO+?F3S5-hjLaY}XuS}DiolsR$euc!n%AX@X%$w& z^$J)(tB1?$2fH0HQXK-qmBU#Sr`RNJEb(O9M2AfK1h~2@yY(72&=IU^{~%`?cj$YW z*q{Oe!?ncO<hLMp{V(LfM}c3SF!4jYLv{@U&kpQOn30G@5OLGxIdyz`pLWEPoq?;4 zVU+n{!dRE5lN|{h_b>?ep;Y=u+N;h-+Tx>~l;(`rcyzw42qs&77acft|D_$bJ}BMQ zEk0S$C_L+**0o7auu0sS?^fgEbXQ{h`<kNZ&cMNUe91I-(2i@jL5csDw&ZNnt>mEp zJ|*@=_dY!3-*Ho&fxYj$5^3(LE;;V1hz!>j{~DC$`s>X`l#2OVuH?VuKpT$XR0$?K zGUD7-6-~GJ4L;-knzrc}M)ea%QhKzr^1qwGKA0@`7fogPw{7&kqu;hIYMX8?exu1? zxrYI`52e&U!k+9t{|8OUe_5gGn*AR%|C&noRwG*Vcig{@+x&Li|D2H%r>^l^D&?<~ zV<?psF#QqsMUJ2m_>G!|z>&6OraP$r%Rn;SJ&ef=evObD74^4y#s4)iXvZ;}I>A3^ zivP>z&^68fYl!8)hUBlf!Ne0I{Edjlzc8)8Bl160{wE@q|2n4WJ0kfj-UYQQ|4m=t z)i?J4uQKj$e5HT!ZQw&{@<;s*u*=}@??`$4J1Lzfj6HF`;VF2!E4T9zHqjBRdjBNn z3U}xz!cN`8w>xtFo3nESwZMh{dNSu<Pi}vEQg*cS{9lOwcL{p@vmX9gg5JyD>i?qo zFUNp3TzmWt@A<!8`STs#KmTLK@5MGDHrk2#kCf|ZzY3h;R^Ju7P|A;3V<Mxi=*Dzc zxHMiZaK>xBN31=Gpb>^|H;DW@@!$We|2SIYUz-iT9ZiUTZ}Weaz9r^8kAUxs-S(v& z8}sqWl;xdj`8p59@_6ywCKh(`*uT49aX<-l&1UScpx`Q>h<&YXhnn%?iNboNe;D3# z=4zVq`lVTM`h&8;DNoBiS>N~yC}fyyT;=T}D0Nmv-!4{o@!^!pN_prpISe&-F7lxF zicdj0CdV}hpb!sAjal)(v~j3y+HCefe)`t&)pNeNV=3lChwJcrI?MN+HsH12sHn2v zq%el>Hz^GEU!<@dQFf;5Xa`?I>?q*?9OA>q@9^`flxFeBzOs|d?pUr+rr>Q>i3pt@ zOPIAciGAcvm%m$AYvPLX#wCQj@XVgBZXG<{fTT&KO6t@t?Bot^0wbrTwi7LunWv{8 z9f(hl6eq2!{t4Ssr8d{y{s5fVRj%!}MX+;Xm|aVu0xTGBUV*Ntf+Jb6Qr93anUL!) z*e6fuO(iMZw{m)BC;>a=*Ws!_U6lz`=JovEqe!oW<9qSEdbF^w2C)WJjCQHh-)9B_ z$$P=Zs^@5%YBKZp8|$DLE?HxS8TwM$9!G-$G26`mU^XKVn;ef&Sb#+u19HfPOMa04 zXq1=J<jh|oK)j6e--WiY?hB(yj06}KEt%Yw^*OpU^Au;arwM~urR;ul;eu#~{Q68e zPx25o_8?;;f!SDBc!DS(aW;wqg7J3Uaz#bC!T<|hEM`jZC3wXOh*E>gUfdug0~;?L zRCf+14dWTKK^`n4q<4TB5Fg+{ocTm*V?6M5J&YEA(y~gQ4Ou9@$cfY+_x!X%-sfZ) zoCl5yM-6E<g^T{7S6?CLiKUK!a008Mk4d2=mk3q>34|(4tZC3V6PHuTBb7>$&Ja~B z=`c`sjzA6s`#5aJa|}H;7`zWhmq|GM@dDUV;J-|Y?i!_HpG|5O8b}dQbs91!hf!a# z<d0F5qyxRy3j*=$4XkW-A`ZW7XNSw71z))0FLOnTFBlMrf+eQHvQrr32?PEXCmh4= z<D@iv1z*nhb}^v0S^A(ypb4Z(FC4c1ytLAoxyAN7Oh)}`o-6<f4v1?hfFAQwosu<N zCUdThR)q3+C3S%lrZrP4QHAOTIO1$D-p9J_<c(%dJTw+<oGU6bl(I=52Lyj3IR9FR z2%}?MSiI^6RAg7LOHWo;7dJ%v3*{UUG{4$h!P_%~NYH2)Q!NdB7q?sp+{bGy2}cYr zGLc-r3@bUXA$1Z+$Dix8@(3wHN%a)gejC&Q!o&c5l=Q@rJ6mzE^r4L43|G?fJ)G_C zdz*Q5_AEV0J~u#x_|e^`|9BXLKqC6<$$?d14iN&&Lsy0@({#;j;5)5A_Yt`9#L82y z+|CgGejz(oK2XpVfr-mLDC!XaT06>(7+*Qn*8fuKw$r-#K=eBu1-#*p|NX3u{y%4J z=L+j$D4#vbZ!kC|EFn?w?YaqZ<h@4<fZrLXq24nTqJAhFjS)_y>$g1qd}Myy<`UJh zl69>to_Cn2Gt>hBOeUzSy5iY9zRS)`V&(nhiNMGWa6%xtbx~AYFd+;J9WzGLwPh_` zT`XC*dVhYr_Vsk!ldZ?wl-#2VYY+zv8A`l7l1_=%wYHWHVeK2uB`dT`BS}1<Pz)I} zqik!32<=-cg(KPitt`Eb@uI(BWT;!rSeR%JH_)jn&T-ceo=~*>f^=!bhW%adF5JSl znE$|(@9KaAOZLR7mc-B(&5CuzoDTfAEdTvw=4Ui749UxObm61RyafwLi(%_Io^;8+ z$v#_DppOe?A9CU93T$E7I}@1COoXiM_64x~!if#*O#gG|s$0*d67qy1wdA~(w4(FC z(jSxkO_%$FqBi0U@4Dwr-q6%l>CH>Cr2)Lo?y);e?&SsQsRnIHX0Cz0N?cMwp_(i8 zcEe<$fsmF=r?J#usjK!`cJuShn-r<}`)J^c{+zeYNObKtUT^Yj)3DVZ?7Bd@SHOuk zx_#mf<mu+L=<xV?lAU4o%l5y2qyy%A*=Ki^1YtX`XWu(F4Sn&fp2Bi;T}_LddKWf5 zT0r}dTVlF(8S-}XSj>~x{+{~4$t3CSdvfCGtGI-n(?NpaJ;Ed!arIa_h2c03%#Ce7 z?=K46ZlJ~Ho-1SuCy}6ixkQDo9R_JATvbAzLhvQ?-!nTbV8Dq?P<hGUM_;_`+kM?3 z!K>FH)`epzw22s)p6lE7o0$U`KOJ8j6A}k%g7-xeep#uI>&voYLlrx9@6~ol52F30 zhERijS$mBGJuT~buEd~u@H8YMX=!bNNxM2Fw*JbG1jX3Kg#$9@78f;upB#SO-9j}_ zPfz)naBHn$W_ep)$FpJPN6d&px}nALnBbIEv`|GJ&Cig{L^DqW#F^>y8JNcOT)k5s z`>~G44K9KkkW?cBTbIf7*ykccsN2en?z52|rH}a_!@m-?7>M8J>?Pjw&SMAQ2=)~c zh=JuI!*T5zv%Ea2?J1sAWLf?F6>qmOqt<rSY`3rm>m+%)^r1RxZ?CsTz7gU{&5<=x zr>4uNc6O;?<OMtg>2!ld!GOA$<a&X&q!!H0-^cqyD_GEi&xt3UDCK9PVQH>$NtC_v z1qXZ)b~Kf^-Li!7u;m=I`pFKw&JTasl^c>Y<D|>7tAy(Y=q3VpgiVJz3t0v5;&dV# z=F$%w!*~5*HZD!bpS3wZZ`fXUb*hJL^h(!R1hIJr>)~zi^cu=(id}H6CEW*7V;vUi zokCJbp9sT}iYKbcC&dpRyx29GD^(-}E>?fh_+37}p<OL2HQo8u|F<S*9Sz<Q10$*Y zmz>0TkL#22Jl)#Xk<L0j%hYsD(<w$y`{`Or-TF_UCFwWb1x+?L(9uAvDTMPtNoOoA z&|Bio+$1>f)$$exdZYt-Q4BSFRPhkiVm=37GkZ`&o<{4XwY&RA>IUEn0&OWE;m$Wm zo;%B%2dtywigBvh_Eso+Zn6{WQ%u@FxIuzFJA~L3%E|HLz~~ctl(_GKe-g^*>wZeq zyavFp-tPm}d<LM4Cd@;AXPF^7B7*rXg2co?hxd~mx@5#zD<&O!9G!c`2H^G`gI?Hm z$W>XrzZhtr+8%qiV$iWjLdM&1@-^E$1q-NcV~2J&esPWKRK2g)KF*}2Si-B1#OL$6 zcHRz)ZW&pMMeS$^eAngcJ6=LcP;(ZeE^r%9meOpw#kobRvVGLqqV<+OneSw=1_+}+ zVSeuhym!I=zKM{o-0N!km<**mH-yQaL@m2jsEA=(+sMK7FTY2Rn;=OKF4IW#eSC>! z!rG48?m-~xA`F@R12;DFP6k)_MU2FAKd*OBk+ucScw;d0J}UT=pI7GH&EpWZ!CXRH zy5=Fk6uhQcOZqrdnn%^V`tbwSob3VMLN^>8+d!ZkSa_bwpajVAK{}nBZEWI{?p($k zN|i(DX#i9WL`vs(D-?!dz1{O9TpSTUZ7<XZ_=ex}Cj=WHyXskyJ+uZ6Z%$1_W8m2L z*ZjHhx0vV={Eeg5l1_dTM(Vt&Hu=1GK+S~|Nnar41SGCN=CZ!j?>e8fnlf4^NVH@e z)~ts<;HSb+(M0guqFi-<N29o3Cg9y~Ab+OKu=qDC&rqpjze_2jx|He*L8Lv!pmgMZ zr3TN8=-L-~h%>Z56<)u=8xhDc-7F`2m}gjorXY!>bd%V7A`HUW3z{F|_GES4i0&`P zElUi{_!`Ms^dFCt6u2^oRzB5q%-iI;EHAfb_kCv|$i-+Jo@G1d=Wk?XJ9`*HINy?{ zqhjO>GrY7@NKp>rt44iJEwv{aBMTQN)1S4m!yb0G#%(6{YQfzknYX$Ixd2!T=NiO* zUe@*<tk-(Rb179%8GO^TJ4Je?#IIEuL{{jI!$7CyJe*|bAYDk&>+88P9ZO5uJn{Hb z%<hoOQp=;-Zev7#IU%{Iay6>Jp(CNwmHAye{Ewm8VxCoc_5PgtDYc;(@X)||Q_3qW z{(Uql*Tnt06Nfr;jLgV&KzmMxEC~)^(fvBVPz)fNh*MA&G*iH&4wEq|^{TL@3w-@o zgJXMrBu;l&k!eXXuno}`#X-WDssLpq$zy$7Y*{)mPl`i`2a+9WBm}re2G-R!5Pyl< z_ALwivIwuiAypJDnk({viwUO4vy^cBmK{_TufRaSVOouaS&3<7bAF!+KKc;pPSHsi zf+k)tbv)uent>-&PDS1jy*Ye}XjHNd@QAaN@`QOkmqR-w^R(DZzS?|$#|SpWDA$(u z{v@mBA*}`hSx<y4>;j(u_?7G$1!G6(h4`wC9UM_1o}_K&!mB>q8OecE8jimHB}tKO zj1H;<-)50-;yX!45AK`cs`DDcoXp4hyJUeH0RdtqRPt!zfg+t4S~yy#n>n00QAv^} z(fR@w;Hxs!JPS3aEfG1>oHNu6qxFEYbu2~#0<oTXy=r&k_koasq@re@DnZfWbo7)1 z537@pI21mkU;o&>cZGmx!5P3{Z}{CIvNS8yCKK1Xy>n_a`J#g1^p%sjg;5D>X|RHB zM=Ns&+a?&QvNU_Djqdo7!z{2ylM*mMbPz3)sC`2jNVg63AiyKy;V7mM^K%$JitP{n zsz%fN2ulE(UcG5e)%mV^M8%-vfPosCALt;9^|y(xmD7F|c4byFC20|01r&~LY==W< zA>@sxVRRImT%eBn^T{^6;f(TCWKo_V@+u#`Aak9nn4wT)5wB~rA*+?Y9q^9*G1E(- z3R)+$l&c|{*+71(L_f5(oq1Ws=am6`I8+o0xaGr_sieE_FZI#i!~*RAr)gps7pK_M zjPDiBR*>d%R)FL~24Qmm!8n$yBN*qOSF@b#(&0BPv~>wRr@kq#wDsi$XJgW=JC{Ym z7RE(7%qEmU-a1mBB#>|bwco%8)&zEfK(%8yX=`Y8Yr(g-2B9|{N5HKxy$GUINEirE zI5XmzX(SzUr4&&fkd24K1!c+hikG9h{EXLjTMCTl7gUs$fg&SRNrfPQBsHu{+%*Cl zr68z(z{<f_w<@FIOS|iSe!yC;&+$sg^9(sq-q)CW5D+5`@@%hk53%fR(98i5Z*=cF zPKcudf?&S`vz*bELOGR?CTtL{A3~yEX!yx&mv?RkH(|+AbcqL&6YGHhR^oW*0;eep zQ>QTh<_DK~|5AycvST8BrZ$Nc8E!bq5>;D20#TVqk_R556xrd$#3}frd-4G#&H%ty zUL~oXBy<6}VTqs!1)X4ho;yQ06>c8`fF4;)ct$m$l<ODSl=m<Kmq=29!Li(hE?bvp ztV6hLm{h}q{ja?oU>wlEii?&nJM;tTu#<4uPGs2B(`LB{jf3zxfZH#m_v0;68H>~a zLoz(;lv2czl|OiKfiTZ5$9xU|TmdD9`7uWocDZ&aZozM!A~Ed?WLv|P0qZlCu56xD z;=050a1v#@HI{b(hBlFRjcH_pM()wuV5FlF8OCRheGXMhkm__w<ReSm>Fka;PbzW5 zvWYVkip4~hi%LclYX?w$4{1HQnO*c(#dOvi*7|FFdw8@`fH%`ikzWrO4++eKeYS$k zg!z>F>(%IO47Pgsw6aH1iK>iAyX{j6rd&%dz1w~dsBhFs^=%X8?j_(kRp%Zt*1fnD zy1~9XCmddt7&TXHaKkf>4|RyCr0z)Nu*@@0CNGiiUQo@c0cim+Zlu91nlb~)T;bQ% zUX-B3V<FXInY|4O;*bZ4_Ctus#<*<(>;kXh#J?oS1B`x|py43-cGiYzt0oZ?S;m;6 zBd_HF56UqkCPYwJa;dk{`B4wd>lbDBe2Fk)m(g@6?;BY(+i()$UezzpCr!_!PMlMo zWf>reB+?};nA_+4So?{0LqLwO2S!_*xDv;Mty1p_hRml7@1;2}eG(KfyW&iR0nAH8 z8Jvn*ZV}8f;FrVPYc<nMjodd4aD+TCJFj1C22jZ|>7-R=(uzZ5%M{77$4x{DJR!>@ zJ1gNv8)Le<L&u&IDj5#>3!(6FUZ1#}Fa<hHnUDmG$NOziuwDVVveYCvwW5$cCWDeR zp#j*<ywGE7f47)`ZsK9Z-A;D9o`0S1e#JIxp>s45%wR#jkMe9=u?K_8CrCBHg2sip zwXlc4JflW2j8z#SIxX2k(7&(-IQ^;k7m}(CE_|u{0D#?8<6sH!5AkVJX)16>6-+Q| zanJ%Y;Pf7&Ud(2!o4oORt+?e%1kt!|C=N>SQ!L|ji}?#CMs`TErZ=`GjAo61l$2$V zRuw@_^YSHu`kaP#rtPBAE{pF9UohicOqHrd(McRo%K=OL^`cz})AM8_k6Mr%{|{gH z*qm9|wTn8o?WEJOZQHhO+g8W6-LccL?cA|#+gW+mT2;IDu4ljB<{y}K%yC`w9H)7~ zW7jAxO|R9pV}q9{HRXwH?g?eT7K*vTy99CEVZE2=JW^I*e572F#3;CO3tUlwa*NFR zS18;+(?+3Riv-~r-$9Ed?(o;)IeE!RhL06bS#IslcNbPogH4<>MnroUvsNBadqpNs zk!nh#o?3E4QvIMJC0{hx>D3arV4V~~byRY{O$;YK7;GW6v8wt+c%}4eD|nDR3*AYB z*{KK1a!;|?I$brDY4MeALrZ*!W}^F9R_vFUlrQ;cm57LxsfcS9E>_9$1=7!Pgig4X z6=z6Up!ag6ZD6me$cuf(1G|?p3Ha<OC?_j-%oN3G5c3B;k~>pyo0BCfaBPNs99~nc zpO|cU*z~nV+tu6o>lS*g`%dRKB+;O1SX9Q{Xtpgi2QkMj+@JGzTIX67o@+L&2z&d8 z5o;Ow@H*E|o0rYE#@}CHl8gX(11NBzO(*&Z={W$OGOEYVQBQ~8SR-vqNXL_&azYg~ zD6EGG$uALEHOxmC>*~9Ghn^7F$6h_DA)GVKeUA0wf2@@qIE2Tb)9=%_iD|!?ib7*l zX=ynIQUO7-7@QJY5`%I6de=o22bV|9uzMnidhwOFMsZjN#QF$YC&-`>-Z$HV4j<I? z5#O~#>;JtYb<n0{GaMBN=*sthIDTOIzsC<w#3PAcJ;?6}c$~3wFB0X~nCo}YB(tQk zAx*u}tUY+Vvnak0?VPgAEpEny2KvU|uQT0)ohxRluCC@nbD-@p=5Pqcpo^NTo}X0I zm5=+|{#nu1g#9iUq_>8;_7;y?Scr*+XxsQp$E%m!>b0MrW&Yo-g!I9h7^NeU*`bbX zm3^kS7j=B&4X4kLkd1v5*<g*sBsj%mG;LEj!6r`e8(U(*H#@>$gNSgl><DCnXv;&* z+hx+~<NyYU_ST;rkqw%^C9j=Os!bU3dYc49oDt?7@JxEIY|v!Qv)7&*Eg7H?&jBpi zp{L<>Ux2tSMFx2DSAXl{>VbCM+Q;iMhBFq~BY@W>apZuW8-JMnq}L@0PTYk2=S^$X z%L5>8WY<II9B-+%=%rN8+gW27r{-8I)2<`6ETjDp^maM?abn^P82i)hbB<qpz$O{- znRlYjb^F$4>Oodr@SP_5N^Es)fIbm;Z}Sd*vu;9?<a2GzFsJc0XQ}QsCt~7o>gP`R zMXR$LdpkzBkA}>fDYmqY2eW7TxL@CWK=UDQ*OW;`q223QzEqnpG@PDGj%?E51#vsJ zk#MgV!VM<u<5Kqt)Z(+wZ_BdT-mRJUb$i0BcKgaLGwpIy-J5&+x>wuy7`@aJdC>is zR_=~%lm8Tk=@u%^(my~``+9g}-{OX^$C+(?(X|6$zy09PcYT3U__^J{$~vgSAUoVB z#{T+<i7!uO0X27N4Zlf7AAT=cYuBm!k3hSi`(|$$_DK7)ar2A9#IC^ubC79Dm1KJD zxXj#F(ioBBn3*1>$gMKrJrFUd=wnT^2sHC2WP2B&Xq6R8Shwn=%(=?Tb-k_Q>fUyE zRsY;t`}EM|`?=_D#NhO;$AJKuv8FfEf7_HNQv}Q&!#;PBc78RrK5W49I>Fvjtxcyk z;mFh_59?0Px9A30YeiGBL}$8CjZc2cBI)U3vtT6~q4m+PNsAag072%1yb*qC%HfGw znvF;|EvLjC2*g7OX=1_Tv7Ifu8}=e$<o3-7iF-VECmS#3<HR>=2<GJgJ!pl&t1oO5 zHVS7p!IuNhY{qvJ8eL-_bgT2m7F8;)u8tX0Qu##(^+|){6t#Z-*ui_0*VTCBnuT55 z^!sKpN4+)zS+{J=geqNaV+)u;*<nz5_?9DmDp@8W;vc0ZL5@nRRN*3-%JDTv4>dHm z&WBP9u0oiLFY;FCZ9G6dvcI>q9{5y`=_-ek@@skSk6DJ<#5TWO+fnx-U&AvmVEKu6 zge`8Zr4~E&ew#-zr#oyX>cb}Zb$4kCs=UZVNdK=<x?K9Wpxw-1UDn26#^ON>JdNxC zOO%j}WSSw*vES{o{>ruGgDpeG*Uu&A#r`$34}TC2A+!OUli0Is*{YSUtD8I5<=2e* zrvJjf-Q}8&c-i*Pd$+iwvVBDk4?+ZI7zw3n3ye<OO25;vz8flnZF+rY0_GP8b=q$i zv@KCbgwib_vVL|)rf&J8)msW2X}*=5a${-!f9GJRjKHOiEHe!xe846n$HV-Oi7fBp z6f36N>J*SDdNf7~HF;l#_`V}T2@=I6?o!d};4aA=l;X586|jq|rRXIvBw*V|B)3zT z65$e^oHL04q}j(ps-;`R63t_U0+w^~Q~*^(Lk|=AcGJ~8ZWS8i5Qm0~J!zn-GeDY> zr#+Cc%>(`_wZnx5G<kypAzFPK+yD2kyXsDqe4c-O{V6f67g~mxuS5QgJ?8@R#@LMs z<Wh-?vB57YWYURI2@M^)EE%FSufv8~xZ%xk^R_S0_G7=7eg8hln+(N$-`9Gwz=My5 z6~nh-!{Xf*N%Syr>w;bPGh)(a!h_R6NCc%w>L~pZEFj((4Fz!|#5s<T`DH578acZH z(*T5#U|O!rdpw^Ayt(1X5N3i41w}^87n3wWWNJF!^Y!A5BP)LKsShX3n;`V!6LDD0 z7pb!Sj)q`NJ?n{KvWv&^0ru8=Z>yR}uN)Dj19AJohGlY}T+43aK{OGSVqgLYa7F2q zE)oPH4HKs<nku9~ER2Zengd9P2CWcMDFu>*-wQ5f+&`NBsmT>bWPlgxDrSSTR%UZ- zQiak$xs5s}`h;7P;Q#W2%?vDL8X=g+Wtab@UmMlbzX~+Z7Hk#JQ5=#xB0sCsyz6sL zZTdIrMar2CR)0gY8y=|s8)>M@4Nv6s_h;xf$h4Hc8`mi?4B{|-o~#+1jJ;|gY>9#z zrs~!)g4*Sn3*2Fzs7{mT)yufgt_6ZC>q>#|la1rUt$(8h^%pqNqHN+=V^lY0biqla z8mPZl<#otvt>jzoa%3L&ol7FLwiJ*@PH5kUqt)o^ut@iXijUtW9)h7b^0gfgqL%HQ zW&heMYwFff?#<#~pm90JbV2#)cXLD$2IvKp4ED*yR~!Fxl!{DcXO2I6&L=Ta1$TpQ zak=j3>XRzCk)p!AM{#BuEAQJ}u$q&9M1`Ha;XA@+2pfr9JEhxt1gIe<pUcs{7Qt&K zj&7yN5!0P2!S;}R<cVV&-t|(u2buOf(0o36(%-=zLgkJe8NzGAtCb>@oYcj^+s);F zTh!35nU*IOyDgyAFUY`6CBqF2__4pA)=v$peiD1i1G`~Zpy~Di4gy?=9jpq)ixt^` zlIV<EqBe0u4VUV^iQ9EX?f#u8YvhS+T?<*vwoJ=!eHxjQ+n!go^W^yy<X}I$?p=JC zT#i;Xf0%C+RG<1>Kex|h5y|PZcU0@1{9><pFRl*Ob;maeAQmlx)&U<!8gON5$eJ|P z14g;zJ7kD~+o#o<Vv+SjhY@}_>2>4LF~>5#;lL)h<cBvp>GB5^hd`3m1$;>znd?68 zsPF31to4c@HvIc#Zh0{G{y1DsPR#NQvEf9pgs0!M=Ol=hN3fkfp}{X3YzT%6e2MjZ zkrK&gsrRs^6H-3(#3Dmxm@3pb{tACSO1=CmCj@z_8VK8$+>lGvcN-U7O?dcu4@gcv z;Am{gnS9r_x0>bOguhAEyckCLujTyrE2dYZC)B>eSdC;`cXUU|pMAt6E<7{t_yAYm zbqCw^V+2S@CM;Z0tR3G)(<N}THRGE%IJcXTDHozPrqs6n12D{kC-!3@I;1QiofLT6 z*N_K=VMRZ~(0?lR&l(kwUW!_>*=kxJm|eU1X%-s$_k<Ewu%Nvh5h5A9l7g9t7{v*d z$X;Krn*h-d9hI`N74jtNMN3*|#gq@KXdB;}T&b>=t72^9lCE;DOLEkM(k(5cdeGQ^ zH+^(nAkn8tkQzG^h|cwKCpMTqG`|lrN9-wBv;;7qGE_GUihrT65gqgX`}AFFBSj8v zgK6i4KFOK!m0*?`2{=-{`PfJzi3vh>ve=O{T+^yCHU@kN53s}r{R`=7JER8-ZZ8w> z2(!ID{GJD;^!JEX-@)>8A1WIu%7l-_WlS5Qm_hTc2jR#>Ykv|O(7)B-Nsb+?gV8z< z^(X48CeY0+Ty$_90ILE%=Hulk8_NE>$S0}-ryfk7+FK1XV|cM~-HqWOXNF+$brep0 z5GsA)*9H}Deu|wh7pA0&RA(hizPBl~lk##7tSC0>Dl9<Mw|sPe2<`3MNxp-<;V#NE zv(KS#1FNHw!lB8eqd3Trj+x?_<SXKd%13oyx^Izct<iFho4M~T;Lo`z@yJ<IqnTMc z;IX_XAcw;zRakR;vSWW&)!@*o+BWgXn?{0^Eg=hV^ciT?0l3hLgO1i^vI3Y`<N-1h zceHUPB8eKV+e~wr!jcUVRqgVgMQy6?JOHNv&XHRkWoAZqC4++{3uuvhU4!BK2OoG5 zF#S3KsAS}rD&z6rtHlNb^6f{!P_z)0u_lc?;?_i&Z8v-FZ3?K2G@`O#q}nk(3ja*U z)YgOjyX#mcI$_hqfYSP;R82UHDZOtRz|?!$L1R9H#h9T_y6e-vr4Q@UB^}2LNJ}2? zEdrrAgony7#i5v(zfPq{B5wi$8)d>WFIT!%2f?#M%86;Px@)q!4J})a(kG~`I<x&f zbc?Cfj<WNMQ!pcM79ypDKpn6KrKO)`AeCaJ3v;70YmPi)kAm=g0KdYkOOh$94*_fv zyQ3rYa$(8%3}qEo!FHAiB(O8=zS8WgR85n7iOSOMy27ol0jV{TMy`F9nw#@B6Y9$% zqwx0!&56p3PTf@UFmjSc@m2NvtC_|xdt<~`Fk3DI^}GWPyc2)|l5UO8J|!2V{`gQq ziEW&SVP_wYy~yi?_cSH2)s&zIbiF`Pdf)s+RQ0|;5I2MlvB0PLkUEBxn!jRADK#tu z_Q#UpO{H^arbzAKNf?pzNh`OQDqq~>@6YPC{wY$|Q|TpR_K0Ry2BmR<#>hv~%f@43 zWx`SbT;UM6v#LxaxPki4fd1mG&*l?EhsN!e*r$i}6{Nvik<>|}&^+u|4pP2~ysiP; zC;{Lg&!z(~ePjN$7ArGAuv#exui7BaQkcn1CMSUbP2E)3WfEWkiO``jl8gnh2A4Yn zYqj&`+r($XGOQcT$Y>()9t=}7EdVzIGnwugZfLyR8TmPVk|!LZ>`Vqjr4C_eM)C@Q zfGuwnuM<l@!PP<XpN0LomXD&rRV|PSVL-7q`7+-Hzg&rj(g=ZWsy*PC(01hbq>eWW z7C>DmUsV!Q7l2AVKk4=uK^Ap_L6ivsX9I!?RM!zIHBfEbwUd$Gzm89@%#Vc0JZndE z1kCzug2wM8)lqtqRvM!7DGaSi$*If&X0c72WaV3bMFN26IDrGIw8PBr-IZ=AipBB+ zVG%KfEQ{kafdNnnecq2sLV>)DW7*6pAcBQBJAU{E1kzhppPr<I`NL8Lf8$Hdunk1W zk{jWAaZ9<ZgJPwPf9tX-S*c3$VB+*EMXJWZcnh&ky6-*-=+;2YLQo2w0Zccfx00g> zrU1K|kwe08r!@)%@){UNVtq12N|7)pn%L4(9Dj!kRe?)7-E1jffIck?D3a-22Uu6Q zdKvqs>TyW;lFl6R86e~8e~Pab3F~El(^z@C)%zL6`Kut)pn=XoC+(uz)}K&lU!biU zDQc>G%fK6J)3gs2BHDrtAlHE)S`-8Nm4xMknky3iz<61OAqZ{-8XzhX_WlOOXPQKv zgc1d6QX~cju4Y{W!@%gvF<7y>C=AMNq8iUMPR0BH`$G-9T~rVS6P3tRzDuR4J&eZ` zf-cm=D!~7Wny8*C$Ou-UXHAiBLF$71rbZWRfxX}yk%I=K2N6px-9+VJb=Gp40YQXB z!HG@BNNgE3@!~ERxk5!^oB|{dE^3L5mKz@PS4wTPPyjl~Fc{PJMK}Xqv+AH)aESu% zvkEN}B+4j})JH{lrXoN|uY>IY4A(>J#1hA+zS^2iBTy1kAZiv6vPfQq$wMKF!}gcW z=;ZXgM!hPcAU)!M9-5dppxwb{-z#+o^}1m_v%!k;GU%cKHPWain*=_C#f*lrwU3T_ z1+QCv4Bap!Rfw>8!j8?L4@MML15#i&t!k{r`|5C--L}u`Y#&yerCqdWCPqxMVA4r~ zfi{R@6{o6(j%ufz*lssKXNP1yugU0{@Y?zvudzm^G7l_^AZD7WI1%46F?b<ciK=a~ z;@l(;A}f_?ElqpKW?O?&L~CWZkiMzSCz#r_d@vJTQhC$?zUmc4`zp6q<RZ^_!dDa# z{FYkDl3FIlrY+0rnMC_)kryp)C{uOU$hi(W;M}sFtpD)idd}cI?L_*GGf)mnPl(WT zF4&}9N=D-!imlIXvNN7W)yv8Dpy6&dlTfxO(zIl}P+vbXqh!%e2pCm+i5@9h2?IrJ zRlGPOcfz50xe-i{#>tdQuKK5zH}h=*2<V>PYPKX|jbEE>5+W~1{l8?xN>MK`p=Z8h zWT?(SM0iCTZQEOxs*UYy(=jw`2CM4Jyk`1!`%KIZ?r1#Rvim@8@081y^c&Z&mrtIz zx-%%OgrGXSe~o2P`v6iO4IZMv)8E5$$i=2_5Jo<S^&~PHcS+0$k65JJVR)^L=Euj~ zl>1oiMsE?fQh%}{Zd2f=r9&nL)(a)QG>OOvnc!E1|6IF2VN6#U2i+mGk*J-{Bo_(z z-vM<ZiLBy3r{v5HGZ4*qP~GUHINYnK$DG%LZ$dfiI9qu!gfBHk(r7>M)LmX4>H;4c z8ZA<lgJeOSBFPXJoPcU3hi;f%<>D446qyQ2{V^c5^Ju{O@<4$_{qUP8+MC7K?yRW1 zsUY1LICvwfR6f(H*U|=jKp|2;6{n_crBt}`&gW?X$vP5?b)s@lJZ>2H3pHFk2H0V# zY^6lnM{E(kekr*rKu8SL+w~YR4j3_V<w6kN-m>@<BViDG9+dMqk>C3NQlkNBO8<Mu zCRv0=Mb=Mw%%^R*k{DqSLZLFNn<p|2S#VoSok3+-9(r>jow3J)f~kCaIy6JJdl(Fa zP%-jS)#G6>mGHoU@6wl`YkRe1GE~ZLGi2EQ$nhTiNsJP}`&my{6;X1(v`)TjlNW*- z*>k_K*Ju1rf+vcg6uew<e6JFO^MbmJ6-&{0eZQlMu0@+q^&!uyrAjmapGQOlE*ZZk zq}VW85RqXzN|KNIQSJPvV4Qwub?PLp<@@3LzwT!JE)@{NMF0ZA6#E~J@0kB{e0QKT z=d}J0-S=ANTd;#$SyE3E0t$u1eg9%K)yCS2yQakCvJ8QCjt%$Ef|5#uT^Y0QCHX_M z{ri00Xx@Xg7f>P!g<v5*ch2CPFrIR6SMN~QF#%AT-z}haU-2-9P(GMqqk5j~uiA$L z5`6HDSu2-xO>2(TVOd(w_huBTedf!p9~zlox(xLRhMaq4t0dE_p}9HNzsHfSCQbwh zN0ykHq$47nYmw#UzTRz5Oo5kJ!SZ_Mr=Ysbr_Wz{r}(lTNKUmy9v7qlGqi0J;<Y&g zwy>YdW*_n=alN2jIs4!-6{&Yau3LyNJ6Du<Wq!Y7b^Y#>O)pgc%+D&9^$yiat<!DU z*)xV%vm~p-pI;sIoY?zO)vEEpistrOyT7fOzAq1>eEnG}vai%;f^G`6JbzytIE@fb zHvb#S5G2}pw0k@#ZdbPO(X^Sg5waO!V~z1g2pg-mk<F|z#=)3EW6%b?jUw9FC3&;+ z$3omEB$b}xEYx90IlZO?Z&u(9V_0gPiOgS}>ae-Xk2G)epl?NgxYSjw1S>K~R!gQ5 znzt{E9DlLARQjX9vnIavki})_ukkLn$z~>Vo{fasU#NNK_*MFEahCr9iYS_Eg08)K ze8c=aGS$T=5BX~?nZ~EIBKh33bglE?f7S9X-uzWfzfrv<5o6C%Z5kMQ$ac{Wx%+1q z`e(<|)vh8x=JF_H15MQl+@RK5@m_-@O)ZHDMvkC0b<;dINVkHoAY7-n!ut6Wy<X08 zF*nO#Pue4dtqtH!ZA}CE@4W>EjER;j8TM7z`tJLd?Sr15?4qit>mw%ed!;^}_pWR! zZJz$fI^?m_+vbhhWi(~a6vIBekp5@IaomSs;q&-a6Q);EH|F+>W_blK_jQ2zW#!yY zvs>rX(QmlBvnuDzw_(fbIv4RTi}^URJ^?mARGDudH$TT!yp7nN*H~15qsr;~+yPy# z@!&_Rl&wN76j4p~fq?F6+@2(ww1BM|6)jNC1xK}s2A2&?AFm8*JhdPLgxts&zQ7ZY z7kzG1sWLxjPE73w7NT>V&wh-VXI*4Qj#vsP(YwvT-`~7ZMYkl)ac?gx9e3hRrSb2( ztsE6_-|un$;01M=n1kJGY7&oXq7Q`}uSNdkp`*wZzDHXMy}5Q2PKzlB)mtx)4mmJC zZF1g!!e#E32+g9(|D8uVQ*K{+|K`W@x-DErsN!U|L_F)9BX9kypV9xXuaTWxNW<#| z&|z-QgB|D%<yQE%?EEm+rOA+Z@_xYB{k*#^dBd81!xK3ye;v$g4>qdQd&udv{ReZp zrc011ieEI9^QLCcV276@g1cujXsqGb?`jO3)13p?=PmkDolHpiM#1ERDdueG8!>8_ z(62*me3;;~0QuWgi`8|pg&%*gPeft1EL>jH!<6Ga)8}bHeoOpzUD=){Rwg&|pZNax zY;CV$SvR}4f!DZL>C@YWVzc)W4nn<UCSMn&wo;A$Y3^T$Hu2PNkg1g{-B7<Ez;LU) zy|~1u=@D#gwpioollp#U^)3fg2S-~>Up}8;`Hbj<ZaaAsIDmv##p8=0>uSE}XS*4P zPtXp3%&>aBi&i#3pl`tA3o`=fea?diHu#_>ZE&yddW;$HRsFonU%*0HLzzF3MM~)B z{S`LlBe=^gZ?7%-qMq_8ul1hC^C>*?^M&e}*u#IfbW@J_jmCZT&?&po_V{*OAuAP^ zgno+%s-!yMTW+zNyYubqMMKAbNaVV*icp$lfkV>Q-1+uS0V}mTvl7rqeM(_%OW~}t zNBRXv{zAAPRuN=wF_Lr9U+&=ifRlDV%CTCFT%(1AQFtYg6<S^*wxOhhc`X&vS}Pv? zKJiDa>-VL`SBtxV%<$UPli#}8x|#XRnp?Vb3%*@_SL`9mz=3~Stiub=@}Kb_I~)!g zhl}kU9ErC~wMIoMY3tLA)OG_KPwi@ov8ydYJ;b`#QlE~W?Mq4Om&+ry-mIR%>-h+l z?g-}{d-{&_Ia>(VSTW7Co1K3<QN^VlO}Ez07n8TR-)ot`AK#wuIxyBEw1VH%ma4Z# z-%NV0A40HJNiGxluV*@zaLfHx65Er9wP&f<r9>l*Zbi9r+F!~mUFW)mt6}Y;*_UOi z(9p`6=$x_7&rsC7*y$I)Fkkdy`w}tNn-}jw`J)pFC4DE#{cGRv$X*rYI=W4ldPb~v zbAMj$aj#H_JF!~V`M-~`TITqUra5T@3%J{U+O_Oe5R@zXOoUPOb~IBHF-3jLSn_7J za1uEu-IxS)HCd0h?pI}&^f1EwHPIhZK@G5;k(vEt?GiA6j%%9qw+>Yq5`VQ*J%hkJ zx(c>;DX!<kifA_^>obcR7WjpJ*XG=|CNoKw6Z5T#OjXg^MeYw-vyMk2ou$cJzg3y{ zISQ%k?jGjwg)%)Y4@U)VKVKzb$_dG1+4Fx-m$~d7-oZ}Q4iwzI)giD7!7Mwjm(zuZ zd1e$v@#xxx@zQKH>^7BK8lzkok4W+P)3xE5g*0O7(wgy0GjD~D<2>qC6CQfnsl=~E zRugO;0@q8=Nu`f5a1*BT0_Z9&N4Sc-X$9`&l$^}tg;H?Mc2P<!yD)9>E=1rhS>sb8 z9Au5M%ZD3H@JC7u)FmlS716H(coS?Q#uKKg+7cT>4J9K>b=YX89yJ4S!+)*Y&eD&J zDa+F&f9E8e@yn|Zkl~yA0w>*^4Xofolrx`%5J;)8Jdz!MHB*R#6luLQM^_FkSKqr; zLyeqU|2;Ysa_YX{&Mo#hnT;FvL)lhU54dMRan@nIwIqRIb)7jb(E|_RjuBZ+sp%Sw zaJ!&9G!BXcnVtEZlYE%I7C)^khnTTd_xoq8076g8A&%qB1coulf=)f08^y|EWJ$He z8ezJ!z@mEGUZ&AP<)#WFFm$xgr09RfkmZ;~A7E<;=Gt`BHJB<{uU5j+Na<|kV79uj z7~LX6^UYC@$QD#RcTXfg>lA5(RK8UTCJ3y^A4fDxvk**M0UPAaF$lp6i+U}Aq-x!* z$U}=2H>$Sso3G|bJ0#xJDX4gNugo@Zq^7ii=O-ulJA!GXWRlK|iB!@Vb8Pw`TZ7G6 zXRugohlg-AMA-_&t9(qNU`=^yMTp!Tsvw$+4p`1o<jD9Sc1jm0XhOzCLA^D;FCkKt zx7`#c%-9gO3YC+PbU!`uys%&bHfz}k#2l^@NDEr2%Mv|9l&=$HxO7ulLL{aI{xsEk zy$;F&b)r+_&#MB=C%-2M<|-_E!<THjnOU-HoM<A1YX2~FQ_ChVNBLouq-C{&%B_o> zOFd(wMA0_y&@dJG;EF18D1a_Ob9euoa_}vLejzn63VZ-V)2MHol9J}WaMg#23A5HV zY!pxI`uaCw%CmJAZ^YXqWencxM{688hv(s-R9~&mXH%xm-+a2go_24Q8~;BGJ{TmU z_0UBTngew*kgUg4CI<y$GEp5N%*#7;peV^DPP#&M_;UCIN2x=$fO=Sj)N@9-thN?l zjKI9?iFz9&eVoh%7wG20cf=LMe;7|><9&TXamE}TxZ#03j8xETWUQ4<e}SsnRNyj@ zG$MoLP>xni)#h(0Aj8ZsSY>WFAyMaIwt_&4;mq~|P=;xA;qmKHG-tv>vE72{SV^2G zdHM3<hA7!H2IlibVoj4WTn1t888b|Y{QH&=md_YvNig<^-h;vtT!z?A9a+E-GX7*} zC<Z$XkgR-Ks%?t68D+1)Z8Jxuc%@Y?8WYR5VZ4B_HLF}hl5G(*+LAECZgg&Rl}BQ{ zLSOCoJN4x)2`%76zG(`fnUb||-H3u=Z504$Yc37e5j6_Pu+V+}(!4Ywfs3=0+u~wu zNrKVOpYConV9xF)L*yY8-q7U(CpY#TI`|bn{+edQwL*@)0TZfkIIR*w+u{`Afy#rd z>WDRzz@A{dVklj!mWe@Vu))+2#m<N7wbR65bqdQO){0U<%YcxHUq{oFM4c^Ba`gZU zL7o-obfgV~TBK!k1CTj|Q<hVV`en$vID{-qK%*I+mdoDicj16Kqz&XMw-0i0G(d?$ zAVolIu%xs7U9+lju2xzB49pb?-4Hkw5AT~#r^g%DnKr-=>;S=5IC{(ve#Hf<X@b2| za|lunDU{`v0g1ueu*gOV%Q|{gaEcSlt`En~f-3`A_P+O&kPC2eFx@;6jp!}*au3G- zoynOUpaor#MmmX3o)!-m=P+jOy^yhql|^+1Bm#q#3Kh+>W#tQpOQ2a|`k=GGL2h{D zXk+@8S{9Qfl{QKj2|+4`T_R=Ut$7&Y*#|mT{{{-U4f4P^k-#&xC<LYLj6%R;=`D!g z7SM3Ludj0+rk+MK25EMb#utpzB13r4-G~fcaVR8bCkigLH1h^l2AM8BoD0H0(220# zTR?|ipKE7rAS{FRr`E-%HRGj|yAIT}V(>jm7X=@3(=CR!3ly~!2Eipm-qcKPR8n6w zOa<NpAv4&e|5?q%A!{{}9lUE#wlfMn8hTG2pra{KZZ=(?(7)Y>2zHEA=C~FQ)YymA zi+4-h3?4xPDC(opEmn+G=58B7_3mP6wTA(w6+%Y%5PTY$hI$oDYn>3Q1SbjGcgO2B z#DobMg3dO>3{e)e>i6OJLgfceG0k*|52>M}K(V0dgl<LTlHK8R5>HdgW|?KjmO_+V zfePH~&BD9E>LLi<+36q7_-&^nqj%QoCF3-U{Q{>3B~%%QSYP`Swi+cgbpr&=?}Io3 zOTD&vyrKG|nLY~M)^>D$5dPPZvWb4)weDvv92(~TFjBJoXQZssSXbH?L-LhZ{SurI z216Td*CtCxdjY8c-#6$JDpAr7=4olBwnmdQGyJCLNuZaWi5@0cWr9t)0kT-w1S|tK zxxOwv38>{cCVnS2sR{#B!O4GNi;p!`(?yOIlu`BMe0%_I(Z^x4T_u!uibv56r0-g| zs3RZB=SByxbBlDc#0|xqm?)d#Cvi=f{s?V&L<)EeEWkH1iYpd6Z57!BXBBe*x1lCZ zDJEExWTg_fljuzDY9SSN#K4V|1G(O7CLhVHDdrX;wJx#@a8Bpec{%Z97N8v;>JMGF zsEVsXSj5_|pTTPeP>z#~->{>NRO}t+i0Z|AzHaR#+dkSY<nRzW-OU7j+|cOl2=kMu z@;T&IfYDQyJLV>cR#2rW28j<z!;z;}JQQ*0@q)j!Y4GLXxjW!d2e)uVCwi;4Syz^t zt<+R=x0q_DmTA|ll`HT<Rg@lc#u}dHnxEB>D*Hwb4@D=eW|pwSH(IuPO}NPb;>YM5 zSaQ&VrY+gvH(g>%twDoj+$DuHxksFICYoJvi-UGQTgI=}ms!n|tg~8@W43#y-7{s3 zCzIz*9X~mw+Jf0)U-47+akIs@Z_%-rB|x0JbB`zy+D<n@?$-fXS2gwI`m5eSgXet1 zIUF!OsW^&Vwc%ZYvmJ<hufdx*VbKB*0z?J50qL+y)GnBFSOAvzQHbTfE6Y(Q?T%mD ze7HFN6*u5BL+63{teFWM$HXSu9F;d{s#Q{zyj6-5oF+{&>#VdLC9N)v&~-o;8^D`8 zM(pW~#!IXz4X|}X50giC;nF*~1x|EcU$3G>VE6$w7f)TT35y~N+WAR_EVd(IOcspQ zZs7I<8|#FHaF&=P?|wKxy}46-bWz;Jdi+SLW+%H>_|uUTB8*8c7FVp3td80ZPbl({ z)XB1xPjkt$E(3Q5gbKI@mRO^Eb{3l5S=2<Q9iGz?z$?#oEs^ZwB&h;cA;uInZ+rLm zib3;0Izggu(%JZl-6qlsT6~n>AJsra)W_JFJk)+DRt+6~`U0LzB3;6ogKwaKKfuRO zEP-Ky$SpMVCrAng4*`qVJTOq*{bNCn^KU8|xQZSbLf!A5Yi~R&n!mR=SFoo9z9N0g z5{GqQ-QbB32fr@f-VzV#LIdyvfj_PZhx1ggrs2Z%Z#@9Vn-t=4j+<Gy(ZkD<9}Q#s z<W2mb%SV;WaFp6?%$cnXJ@2=$E_2f~k)Z@=h!7&D!q0kx5~c97$!=<Hfj)jM0fW+S zv+eA9F%oJ%gF3q7^8pNf*G%!VOX|q9zihlkJO=_|+e^$<C(?s~!)@oXOnx@Q(3@g6 zdZg&emwJ>dEx-VmNc$_da=zOOeTAc^>CjPdhW`oqQ|a<#D0W0yTWc3qDs@UxLNuPu zV_1?ub?mww6oDpiZF;D~flc3URF}@bCqtFRP2#eWUPII+-WPN6OR`ZZR|zKhqFp2o z%Zyo~^OA*S6^T7TUEcO<Y;57(8o9(JHTki?I14TIjmED;0SKl@Qr6a0G0*@;XYuR_ zr%hyR;hKrm!O4R=aq&DKi`+Olg{qsN!7rqMgtPPYxyed><!PTt5xj5nuiBdrGfm=z zBf(X_jPPv~MEc#+=a#AaiF?p`7J)DMkg`*Qi`TjPIFQ;7cq}v|2H-DTa;u;JG-&se z5kL}Tu9mO|NXsW0g)V;c9wv9AAEtiX9cV<F2mG0tqW00$6b7j0PhrvUz4y2pbI>Yq zUTn{T)GjIo3s!n@m_;Q2tbTOSFS-24o<6-{1phx#`mR&lNY{F0TkH?PXByg-=Dgk0 z20<`}hEzEXxhlWY>pjh^+O4^ak_%Q1tfv`P$`BS)Tj+Bd+M`>RllB=@|5@Zqlzfxl z{_}BcfS?_L_YijnK%~{R-DB6Rj1k!y&H>M+v@oG6xizQsp`t6>nU<mzkN?|jGQ?-T zNR?iuwK}i}b1dK_Onr|-0Cj>rGEsjvAWCt<Ke)L9=_)_PqFr`&sWfGECGh?w!2&mB z*9@m##!_>s!Y1HJ7fw3qtUJJ#nQH0f%GGBAyWEy)>{SQp)gbjjhry@q%nO>zKfx;7 zzO;&dE>%)avCab5VcAv(F<MyR+-BN)1!GOV9OLySi(3fJU2BXbggo5xAPx@IGP(A> z9d<HPwwxpx3tR7?gt}8#x=$~8xC@LBdeo2=0=9dfd2)+O)#XDeQwNP$uW*|mg*4XM zx0G2LfGw=z2Wvo3ZSUa2jRu&{0v%m7nK+-spw5{6wl$+~r5|qbr2uUwEXz|c{L~&u z)NC>IbcTOSU3l*k5(#fIMXW;Gbs92Mqsu$PB(S{W9WjZGx&Y@Bsb+lxF6dR`k`>Aa zF1S&5ms?I*bJkcmn_le}9T~qPrH!@Hnk&U@TBOEe1%0cAQ(+-dNU5wSEk4i~eQmNT zC(6S4Z88xsWeS?#MLV(Oo7QYN!hKyaqSY4Ax@Kv=F*RXSiTFLBJMDeFRyF%5Sa~tv z!c<yGd^z?}WJ#Z)y9*+?CFG~~D@^O*Tu}Q_rQ}(rhf5e@bvET?0Mt8x6LUn$4J~Hf z>L?D|jIEgaWLlyJEUM>5)Z;$?&GP*~|GzrVi|Ciz{o_2#|KvRD|8f2&cq)eWwVlx& z+#d&n74O<poR07Uk{jS?E7<SCl_9FPE@e_pH3oJk<{QJOIddg-H=YVDGhsrcm9w5Z z;XXY#W-mV>;WcQ9k@%0+I+9A717VaU`q<VpWhPcv^X>BCI8UxdF?GL5cn@#HlMGlp zyZKNwm;740nnq)XbUfJ<tC-|>qYm_FgQ!^}LvQs6Qb?<dDnMHrZJ6oUH_y&732cPO zU2L9ooH?}kUPvSjtdg`G{8z=Mlw*h!1G{#J<ZyA<3<}y#OLj5G!0GQ)d3iF}<ejP) zYWQ~B)x?rtT6^m)JY<!4(yOfuh!{$Vi9qJqlBSmNu4<Zcso)vI*lTy2#oXRpW;%JC z%g2%DFFX~@L8zu<{&Kz9QOQ*(n(Z*gJY#vkIT%g;5{k{ejg82AKDv0#_bHOVuisHb z^N-EzYA>NHFS0D(@HpiCSie!BxXDbVJf}M1hBv^1rw(|)SsZc5q=#WzcCwXyl$mkG z7iJbUOykI=cM?0M`nfSpwd{yBJ#RmZa8V~gss`{1H_MDX$P7RT{oPK@=oeP4Z&`KA zkY4=<oaJMF%hNwerHZy3*B|0-ah`r&jvwcU;Wx5uvPJ}bb^k~s?a+IT&<CnbXE!sv z>3b=lvg#cIcflu;Ljl)~fuXpTrD%Wh%=bzNIq(S)mMj3X(QHalB~_<Y)B#tPeiSIc z)_MCp1Cn|$p6>HrMfve}_?Z8YKLV_JDOm(N?U!EIeQHf%>Y`=JJN%g|#wtv4hPh>n zm3PGz2hYg{sP4DZwtn5ZO(xX=W`6XQ()1oAQK=Jrit{S2j?P<2{_naJOa2u!%F@CD zi`XO1)JmMv5^TD(HvPMnhu{HZsRSp<`D=4UjyU$K5mU!-mUYO&NHpH9o&ie&2nwUb z*vH`1?q7D|YkIqyelhc$qJo*Iky5hgemt5W0XVOP{}^c}*_~fWJeg-W$AG`KCe`l6 z5{5uzWF5d37j=AtZBU8^A=7r4zQLyvBy#~UsTJg>c|D}PCa1)rjpUX{kdY@f+v*B= zaLU&|X?eLmn+D1`Fq*(6LB^JH`V({o!-oKL9G+AnpO}60{bF#c`Y_^Ku238N2VB?( z3;SS?YYY1ZpjYdsE-f5q>U5j{f{4%8sVVeI$VgysQmC6Ae+&PmHf~rvpCN#4p8EhC zuZXPuFqW+S-ic?HITAn)UT3{_nz7hsZrWli^6N@`W>%g%ih=4ff02hoiSX@(N{F~R z?x?Aa34HJM6$XX=N*Hz9i}jb~7qGK4r8I0mSWlF|60w%R%2mW&EHJOD`f|lGqc|wS zhN(MB_1A(g-B~(#)rmLpkZ(?*B9w+MNA}wkmk;{Wc5U0Bm*A~v(;nY~2)7ueCRt~1 zJ7uPHXjyT#g!ElRNp?$KS`I8Ria^(Kv-_?|JIvj#)oXxqf!&pY<v@)f?7YujoI93A zL<&q>|6|Q8XqY7|iUT{mEZ3m@cx5>w;!yF~XEO{cmu}?KEU;X}&rg6o3JgGhcB=}8 z+bP5u5NoX6+^cm8JorwSemnU1a3fEh=R=JsgE3oc#?U8=@((Kf^cO(nSv=M-UM<!a zy3M00oGD@!{`Md+@8g@~K%2J6rnL8;?Kq2t!2fpJD7pp{>s#(Wz06l0tPf*zkl~m< zQM2vs#D7TRxx%Gy@l^%+m>R&efF$fshc)riyV<9`z&O8vtm^D{jsOvU5=&bO1G&-G z40^cJk)QJ<W02ZuJWUg$^dS!Q0}Wn;n!4qFi*jrlM=d|KudAyc&@cQ@S9OdVcc)h- zv^rDtowRLg#J0m^Pynw(!;o8@tcIT4;1NV$T&i|z_yM14X_UVhLA9boT~3p_X!GfT zM1q74hXQ5yyU{|clfxDwGYKPpcC&d1-1a&I2d>sfJ@_IN94cO8Wyxi9<FN%oUPY$9 z`c$ZDkaB}TMcma^o0)YaA0!_P1M4{Qv1TnwWic>xjM-p_X;npuj09?rH=NF;VxOz- zLo*hd*;ZCzRSXTOur1S0JFL>vNV2-jReFwR>PBX>PIXF^mk)DUHpp|?O?IYE<;8p% z2U2~g@PsP1#IprfC3iQ#Q<g1|Vnua#q(NuK*U+(vXW|J@!&Y@sSvGF00*^#b6mL>? z$bn{t^|2o*sb2M}e77$a`r}(04|3vl$|iL;TOQp<CSV|pF`xKwu3AERV(2{;C4DBV zXoPiP4@`$2PPL$V7;?fU5y&N4;Wj3~v#D+KHm5$jKo4JFnry4{bnUS4sl{5I8+#-} ziz`A7oXb&jA%*N*8kLP-P-SutpK1zzfm`Vs(i$aaqe4yNJxH0B?;>?k`5uSlCj_5P z<Cx1g09g^!!cT&>0#gPp>n=g@!I}dgExpBCYAtnc37yky^pFg!%T;T7ezkqYIg-!& z>8b!$8LF_Npk%}_;;VkA8ZAwuG>!UUzo-4*R@KBx`v|Tpt8ukPFZZ>j-RY4-BOv@+ zFzr!IcO&`IqgvZ<F8nlG22)}!$uszBD9Sqf_o(*hjP;;Gl%KN2L=DYj?uojkgwvYj z%K&d@z9ZJYgw%F8pyj6o6YgCr2%%kao_^?1|HY`&SK<4?_l^F)s?SMZ*_Z#L{u{>s zFx|8L_vv1mES>fSTm^Qx9=t<^Dh21DzFJqaNP-FWUHmo1N7KuU#M5NrLJ1l%Kx~uO zmv^%o@I3RQe*b^e|NZ}{kN<zv_ws+bmoH<dm~dPpb&TETNeZBy=DH)ENlH?qhQ;vs z!(@sLK{+-`qYlzwldwty=WtF5wo9wK_^^Z$T0iM2KTm6yxPx^tEzvIGSsL%67jcqc z&>Uhd7-7|_gjayKFoj{5$Y@Ef83eMCkc4K=;oE<=dTJ7gl8x?9TDzP;tBF%c+I>Fy zoV0cE>LH_MFnenn>F9F-4u*E$a78sS){qb27=aiNZR&@A*x}K<(??O*XCkc>Nv#>c zlD|uXl7rL42~lkFySsJP<ITx&b3~IIh~<IVmv3;{PH+`v#+Po^Rc3!|xx#X)6npF~ zXBkqZol<jcuC9pPaucnH+hx&bU{YpO5eMK;G`J`)MrYPh<4t377BQn~E5qxuvZ^o2 zOfZQ$E|VZNH^NP@;f>T=O5icn{%(XtR2^C~vsPK&wb2Qb?Y{RQFf2`>io_K$?B{KA zoxJ`(*blJ(2m6axr(9BwofhGn-jIoodq;bHZlu}^e!-(hf+9OK2z{6sip$G#{)hLx zkGL&|p1@zE@gPW&6y^WhdYJ!f{p0ftXzIp~^`FYhFUm19{?k3E&{`#wf0*cmS^l^6 znLpNt|HpbkDXrAf_8P<VmJ)6txZY{r+56h3Pw+-FWz}CjK)NpK;l-4G|G=)Csg5hI zb@g6<w4Y7c^sA#(k`awR6)?&wHRBGm!?i74^c!2BM??^(5FJOC?8y<|<=Lu>o1nUH zl>C|Ri%)L9c;)UH1dx+lstB7N;2pW=pKxaGaFeGg-{!Iv@?msuAr~MF{@>bzj{!ga zf3??R`=huQEbWl-4Pu@~LT?E!y_obcvyEcV>XK2o+ITP&NZfwjioQTTu;85>Rwc}k zV<W`^ESqnt5KVgq>jkEQ#cL1>5l0HOz2h;&VHqe@l_1e6_iMXL&^VCb(iZ6&|56r% z$bYI^UD~(?yaOU1(#YS@+k46>&8Sxo8sy{pdX3Qa>Iwu>|HftgxQjtJP7#pBC<a*P z@LD~(HCihOw0>U%4@;(9!=BQf+YUYf=dy4|7AyIg@54lJ1itk|C5j*CvY%Ak0zLfd zg9f7@XFFT<WF<2Fd;c@v2mQ?V#PQ7$7)UMLg`LI<K<jhOXRecT0if=yIU?2kk9v_@ zX28^Mxe~VuWfo{4T9^oBevJtJQ166GI!0ajFDRubWOAsFSS2PIHz4OF7If&jiB80{ zW2mW4Ykqp}bP*Cjk9td6p-HDyqv4f@zvV*P+r&!{e430bd2dI!(WnL`Lh<^ttsf$O zn@!r`WST^14_B5lUJsI-egDUL(a5*iK-mZ(=cjHZBA~z@>r1_UtpBdIc5%1WDe|~_ zOh;je*pd}_Y^UhIMJox7tQEIHGNKSgu-+oqs<xI{$HuL|gs)eMt3ZP918~4T8HN=& zi0(^yLh60Dv){FUZT^$rhK>%h4+U`7Y7bV2k^Mj)`vZMI=RJfp!Ep@cdN(ET$I~ry z?`Jr}5y&cUZSM&F1>E@s1Wo6#^9`5ql^EtyCf`dn4WkuLPuU3#Di-?9>w|L<8lR&O ze_)?w$k7Y_=Mab1A@st9y2jwjF1_McWyL@7!;a;j=<H)T-!R+RDqI;XRyxQ^3{08L z(L9)$Rc?Hw)wvpH+pG-(y7d8}QaDx0Ws34r+MJ!28x9EyCOq1on{S#-4Ekw~`0SRQ zh^u_s>3?18tA}UcV=(V<K$Ueqr}5?Ihp@Tj$wsg9p|cBNRwFK|!p!R}R)9lz6u&^n z8jb4;TJg}5N?QSGM|Kp{gBt{)WoB7tR@p^(?PXPm6ryq)n8;PP&6a(w_L|kJY4m9} zS<oep#b(>7g_kpx<v+3gHCHXPxXJd*sqB83u$cu-vH(|BFY?WuHCd^^@)o=G8koBP zwJIyemnHOIDtO)I*_g2ko~9|ax01_~(!Usy4)9d&3l;t&JfGjyfm=>~+*zg^^sQ1j z#2hvjXXuozK{JpJ8dh|f+ENB|>?x%?x42f_KecZtfA?B&2&+mZK?E3uhK6VMW;Z5Z zAT;De`0@Cs$+Wgi+}hl_rY@`ojBGlN>GI$Go>Hi6{1nR5MR6;#aLe6`x7<?+xWZLq zYSkteaR#cYax>F#4E5UvP5#_Jfb+@QvONAO_@~ApYf&#OG*^3?T|*LcTo=un)0h(} z7ds@ah4<K0m@|Dn%FZDB7E)Obold2uDlGY@9_ik4Q&f<RwKSP_k2URcVCeCvBbt_b z%R`-cArib3Am@B;r-#Aq`|=ZB-tt;@3$>F|7sV}AX3&MSls?K6d%xX-IEK2zxVwj7 zTqx9s;%cCl{xpBH#ZKH{nFxn`aXSm}GscrVmE^b_{mX8;vW#+-@BQm@OZzMIx_ce+ z5pl)*8w3dG`zOq)APowJ1_S{F_2c|6ApM>~XwaWWxS!gV|KG|M_W!s(skv^qE`j2! zZ}1&>V<hPPOWC&vEETewJm4~?K3V1{;rAQ{V<Lq@6Z2~AHb8ccm{e;ZJkb0COm6h7 zLMg9CxonM`|AQ|NoL;h9s&R;@7#TX)gmIeebk7or4>N9r49<_^^O#@mYEL!c0CTla z0%M4(w_(T%NiNFhwP0I6rfiHyPxRg;+r09kK4N&aGmn?AbiepJvEMTfdHn63LpS7b z4e@pQ%qD|o=b3Th*s(sc;luKBj|rE51{p&x;_P^ghA#ffDw9rqYdw6CiGV!VA%nb< z#9y)>khII9M^^bbvjaFJN;Y&LoJpDqz8T^1{S!s<FWqz|i3pCbnl4veluf^<v}D(h z`$82fx|<$(F&rj%-6a7o>YACykO!^?uv+!Bt$62M|9X8oCd!lr{v44&*B#b}+4C69 zG}UY%get4DVz2SJR~_p*m~t4|8x2LrTAB5kEf@K>)fgFB28K4eE(k{I+>9{k!BVTL zh$*yOOv#dxd4B2AuJJbaA?RH21oSIh#K~PVgZ>X1d89U3bBYuLELvU7q=EPD>Yiol zOOwRr_9KmMs&~`92Y#{LXMENCH>vw-@f0dU+w|RmCF8B(R|(-}tGwmI?b+4Y(f#Vc z;qK;5Pm%m{(Li|Cy)c=n{pWMJJI&afB5ykA>qo1)yW3uO>n1#ZuC5+F7r^ym>+-l` zLVkHUobM>DaeV;v`H48o^tDg1_K=dBqhFo3f!}4|^dO5nm^3---xetiS%$ywY|*84 z_`XM~&d5IT#Pa%8@9H4E?#7UO6k5tf)K$Lt*JoIx<<pe6C7z@@I=d&2q++*v)XTD_ zk{z!{DRRZoRld8g8+WIbi1#RrirwMOaZ`Ft<@E&7hTChHZPt30=F^nDzb6gB=Q7#A zorVozxR_BbftY6}5OwpxzRbtEX?C!2&$UenGuGaXS|z?}44bBh3N<dxm*c|d4v(j# zS&_}F7g5V|kzK%Hbrd_?-IA4k;9=V3ob|?CYWTd#zEOHKehxQVr(}bt(!qA$m_B|! zE=1>y&Yvpx(&6am3~o-_neatVo4R3NHl4u<w)ebTz^#3n=SP$0YrHS)Pr{haTT)Cj z1GW0FiHww?p&@cV$`RBmm7pOK=WmNt`?3cqZt>UniHc)shGDdX&uyh~MOPH7G{J;~ zWz=wQh7if+wlVCR#Px$o&i?0d_`P8Y$+~Pqu5ykzRpybHbUz8Gx^!Q3Ik^C_9E<VN zUZ`5+@^YT7Tcwrd@t0!JzRrb~?v<AAtN2$E-n?F~H!$V5$RLs^2PW934CBqsRqTTV zqP0YvNV4@n1Y0E5-)3(=G>CH2>lObRDzFjh*22p7*3N72;Oih{M@!n~H47H=eog%B z2&*9q(`cLuOXFJVDwh0y@!ufjSOYew)S{t-f+kB0bhs3quxcxEqX_Uo@uf|}HGzKu zB97u|@bX{{`sEa17=y1WXh^F-6)w92^~smL6;Ws@D5+w8iR|w{7rAJns_Hrmk)ov* zN)=L_kt+6?KraqsnU))jQ#Oa)&8cYER--H^We4Dtbiv9X1N9UB3Wz8eF97a!ILQab zhA9_<PYwVS)BaL(KvNFs6f`FSt*&Z)E806li4f7<y8u_X0*WvUlO%>lNJE~@L%N8^ zL-$KmurMb3*Vlt%s<bCwWNWGuTx<Bv=iwMBg^V)UJ7*6BR=%PIQAc8&MTH7>eJfxg z`wyJdEx(7ZMscw^_t~gWi($9iiwv$$BcH@Ecpd_^iM<zCO{8@{CZn)FB>!6jEvHyS z%Py1DlthbWjiDkoLQJw7QAEiy)C!|UtJmD`dM=R|JgDyELP|>LhNS^S6;Uhy1T-gt z-pX}z?p`m#1y;nO0m2pdZ{T=<*<az}jD8|>ut6B(OC-(7QZyrA0ii}4!D8q@<8nB< z5)c0+<tq>DWdp&D*y(5#x#t~$z{4B@2V&y>ml&!b7gQ~;CFYpU4m8y?34G8?@YyDy zlXe6S+!0yIE{r7f>%hC@|HaoiFlV}TVLBaK9oy>INhclKwr$&H$LhG_<c;k&wr$%s zCg;pdP1Vdd-(PsDp1s#v_qDIZSi&!aZ9^n~Pge-@lH5<A5x~4IDDRl5rHy4jRs00! zK9y^V7W~_oQ=1%VAyzp=|JLZG6Jq`Ex7OW$+1~&uvN=a1zD?+QOo7#yT>oG4qCwQo ziR36K+B-Dg;Vn<t?jm#hB%|T@kzuE8BRpW%zSaoEkAbI?I0BMhU#IpaVg&e6M!T{& zm8BO@z+;;+D${<bL_{%tJ0cgBOocr|E$+KS>NyjMT_~P&Bw6e5US%{l0{&<x>Q35D z+O3?(b4vo-JH^LN=i)GTJMvh{1A;J|J9BemP9%j&$AUxSC9Ll(13KMo5Js}8hQLR$ z>+LARxto1QZ+(?r51nxW8gg@TU#RAKQ||0TASZ&b(6Aa$GqeMQsRAN}=hdaHalo|e z>H>nXJozNyl$Z|@FfSF<Y%@ywIH|E~@NUtXYphKdraLe&D4`eO8GE;=9M&?u`$Xyp zr(zythwPqQ1&g9iZ3@<Y%ebfrxLwlV$;(Tp6U#)4wJLk4d@}2^it~%sY<J}j=vMKg z0M@rC1Sq;3tuA_a!lYaWx_?|4uDEp#$Vig5t#9KmJ0KF%b2TS3Z6t}w%bGVg18OkF z-8g=(ZGK`m@&3kJ6<q}OCM%N;uV2G^PrKTw>U^6yvVv2)SO$*w=)Luz&V9VNqAZ$x z6g9s@=po!^0B5-0`TpZbV3S+~Y2j-mp!DB@0M7q=B(P0vNc|NASTj<8jRcqmx&w7+ zk|hytmKG|@70CXCyeasS>ZN33OMC_XZOT1hxe9KR$6igce!jZ>Gv6m}!pU?rl$14k zB*aY>YL7-v4Sjb;W+u%x{oKCx7lE4bCZKUrJHjj_wEtj;BJtTg-rGj4lCLb!V<hGJ zgDEMliqD)QMts2|Le!oQNZ-)-OBK&``Dz{ZZ{Y8gP$3`xgG?)l<b_PTYU?ZTU&;C> z@Na%*mUN;x_xTF^-S+=y;P36qt&{gp;6HrnW+Ig`U><VWdIOc<qibxOCHb6M0-ajB zci7>g8>b?X>o8O4&38VTBWL--$N4@i(mp`xE1n*$|4Rc_R}sWfqHuJEO7g1SJz~2r zuFwE&FryfR5A%L@*#c>>hV^13k?NUB>ai>LD0I7|G2bJ`w55^G4M!O%T2f7W{+?&G zMtU~*Q#<}di1i0Q7qaP(qSKOWEAC2wWu<9^u^Oi)Eu5$sb>o41wn1s-sb$P=xHBng z<dJkQGw=G9w936Q)Erpv3|y>oA#rwHLyrsZz`!@_+bqf`EEeUma6>FH>ZYj)CSbmU zchv0>gSU(+T>{9`;Vn-;@gjaE6pi*q*5(6JgWFDW5M_ABhtXP8{*_Zi_{@eh<gWe| z)6-Tgk`@*-C^5)DSW+1T0HGCN9%_LHZM>VYb;V=6U9pzH;Aal&XZcU?uf`*A%S!4n zVG=%KzcHeWrje-n^|NjW#}}rhTM5zPAPaE+MjV--n?kBY4Eo~vam$eP<n^3W9aH!! z^>JtpEw}V^A(C^Ax<+jMn&=eaMRWp&*qtY*h~U5Bq<W}dyWY8peNQ6Zk2Lnn8k{Cs zo{@{5boC}GgP%_9^~yw72sflS7WP}5Zt6RSSw+dlT%^*PdJ!`cRABR`dE=+yw@3%i zYt~9nvpawle=*I5Q{;d4V$tfwF+qbfv5Yhts(d)Ry|ws?{iOo|PneuuWIn?uwaR=F zpKo*mGAZ;dtbQqPWXi%$@2#P4#%%WlCMc&U@ZW!ADe;5K1T8Ev19PSLL#M$~%&*kZ zx2-bEu_<NbBN|X64ypUv_^f57c7y14f_8(BQTObYkZ=sf6soOq@BA+rY;8qp3C$5W zOCmu&Uo#%O65E>kw#|4}8$9?%rj6Df4lweUWl(`<H%~C<(_Ia(C!j(@Dd*7x73P-) zZ-NUVcG&&sh?+<P!Z?CZdV}Hx2vdRVYX8m!Lc63wr#jtqWh7MJ0=cwDX1;NBU(Uo& zt#A;~NiF%t{+k2nEm|Jbs1pD#$nrjx{HSl!xXRDIQUIaEJQe6%dIZ^XQ+yw#bH63~ zi08lrDJ;n}PMJQY;USuRn1#tHK^i)8bYabSN?P>#_jX%t<aoh(uVr;iS<};MtSYrw z&hIw|1S(i3@udVcL|w(*=LF`3{G~}$RP2JWoTirN#ecU$n`3zVG)d**i@S49sS3Xr zhKI%22J)@!J|!y$7-EfU)dFH+!4tPAv>wfdp~>NZll)tId$u$^r+=|1S(1{G^&XcQ zd*YJXyPgRxHf+mQIou;i3T)NVvoYRCmhcX@iO1OLgB6gJHvaNq-BbE9G`HbTxq;^E zn{0ovtH}h`%2=Ef?TtsOMkVcEF8GV<3d}auvdEY5T}mn}pfC*P(1k}~!CE&(O@w<% zENMH)Yn|nW<=6lbh$~<whGz1Tx@{b70LuH<>l{sp+}|l9{j~I4h>w5|L%ggL@S7*N za<j9m!R99km9^cI_0u`Abk8+B8!d;%)FpCw1>$o5LWRbBOX%;EaN|zq9dR|t5R4N# zNEef0VNj&z12Bc0oC_HsMU2_E;i}47O=ZO4wHc9YFfk>BNh`(;bR1X~M!$X{C`FKh zMk+t~5loI?nys=O*Ml(}Tdr=CxT=XFG_rS22{iYnfbVOnA3MuC*3JAw1ynk>ZfQFx zhxL!JTF(}o;^@E-o1|c2M&1i`{4D)?8Lg$pMq7n)OBD7<-g<|fe!Q&DG&xHxDIh;@ zi8kxR3#SW6DaOuD3(AP!P}Lu2(Vk>g9(2ObIH~^)6KW`R&nnfxD#U?{O96~}o>b%k zIk&7l?MA!c)D=>MiHrnA9j&uj0)t0M<eb5UJ*#ar>)EmDVn4YlD50w#RbB3P>T<S3 z2o@+gpJqJBW_tfzrGKB_G5s~^pg;$htF~Wa{xMgejnK)^V-TV}*Obs@y>Y?oD`W&) z64J3#HX^cPQ+-`TJ(mtc!$-y#)#xVLsjeEd!0|J#E+W0DRL922PN_|I4GXvFl;Bto z2?*Qs;0DWysV43#<>Lf*pFQ<DLN7Ad*6PNdP`we8mU6H3T1f^!o|;x}AK%fXR$c*P zAZ>2&<2Qx#G1K4us`E@+?ux_e-G;0>mEVX3`%>w6<JEcN%ts@Oj0KQ)Lb&{M!Fe>M zDpHc?QN=Q(kju({BpcPMTh8ocD>*hz&W^4J+E%P8(z?9vS=}xUzO^lees0sBR=Ay; z9zFQ0J{WK-&eA157;of44d38kLhoSg+~U-fiSY3_o2aEgPTu9S3CAITLHfD-hm3yn zZsJU-eVi`;VfGO{KNe#TrNeO?ws*qdEx*BQ?u*U$`gPy`zk>t-x8+&sS2aNJ->Lzw z|GOIaU-%!%KlooI+gCsUmn>=Xf2x7^{5R1~T3(g}o>~)in(c2>C2hr3#ceJxtGCQa z+4c#LWR}C}c_XMX-pmMj9#Xm{bw*+-zCT}|i)XEpOgSwQsd^PsB)c#Q={(|1t|jv0 zU*Ot>OKaiZf(wjOsDEj&4_RY8HD9CoUxij$dKyIpiXXI74r6dcIDk8vk!j@7s)#dB zia3V@HG3L}gd1>S5EUR#ilT^Q|I{b4ivF@4b_i7ZB9@-doNM`+WN(3rz}bZV84%bE z8`Xj;k>v>gf|ijNMb$VdK37SPH<8VEI7#v5I3CN6<r+1M-+v;}-rl7lb(c?RPsMU> zkshK+DHK57mUuwzi6EBhAc9|%Bk-LVDXyDkM-;MHN)QupW{`5D(@@ivazT*#O52>Q z2jg(j%fd1xo9NUyRAy`N)~7`B<hoh47-^&&8j32oLTy<Qdgt)t&tRc$MFx(f`<Yn> zot2wPmy2wgN!I<J(D(6(X8B8OIM}|ITMeK6FCf5Ns^#fD!}@TVr%#$%C20XJDDBnk zG)+%|i(r@aDW+<;UKBO$_MYc=&7@4&Q~-Ty)8Xz!rz?@}vQHq-F;D0@;}!4#=N}-z z?a1Y_L+*Xs7ePpn=-Y)R^PDoZdc~9ugpza}KN0SR`|IJaQ9BdqPM-y|59RIK>^crm z0XQ~dgAtSP0sD;}73DgbikoTyI*RxTO&K8;ZJh>f6+7nMW+?8B`OYT&1?w`J0!)zT zi=*)ysHsBdFCJk2Tu;{{<bNjwwdoOv&<%8}4JzvmteR$Y9>6V|Bd9RaB%=LDvdzg} zeFB@Me|UhoQ>eU~e|Z22j3+ANe|P}V!YzMCu@%HC+BVVQOwcX-z2H2{P|}a)TJR;- z8{zYTdiu@r{rIPK9!xT@D=!YMeq0X>I3l)gVr4a-`}<jmU_&JBz^FG&&Q&sB2}xy| zoC3d(6p;ABQSkBXLf^d~d0z~mfX_b+phQ<X`0Q|z@b@Y3R!EVh_Rd@x{*Y-f)vscJ z|B5X}H98Taq|Y;=?-Y$0unC}>%I`qf>B#RG2HmS#ex$J&YtUZ=x#K_kDh42F-KZhY z(22S}KmNXJ?AtO_f1=AZ`E0-_-eE0tU>mV^MD(QEWcvzdu-e-ITf5?FJy`NRe!R3{ zcJlwQVF;|A3R2yO6X!MG1|Ub&6Qs{BjL;)bGN{J6D<Hi*E&yO8YGcy?QW`kO6i-=D zwW!m0J+5NT>k4kH6_)r;tXo|=&XfwLuM<+bgS7e{tKX>!%Fr4F`A^rj=j;}?kqkb2 z)1}HW(R6xvdipH#2=aqzjdb<1z{d;MloUk^$US-#MOP<JCm>LmayLd-+U%Kio|o+# zy!#^LIowL7{<;nX;^nyS;;-y87iyR2uY&OI#A!5Ugqq^*nBJh4$p|v#xB68CrpYGf z{c?8wE~uC@Ql$*HWxxoau0|X|jt)ExT-TujLmPj^F9MH=v7^lMv_ac3kJQ(ule|t& zKZ3wINifp>%uc!Z5)jbJD@)*b7vk6vvj(m@oLu%g3k5_&{<(R0Sr;&yv-4`&rT3v^ zgpI+FwR=712}jQiFzP`OZK6rsw*fZo32eI8oR@&E*bxBvNuRKUwTape(J4221ZyU| z_=mq`3qfNQExyz-Bl>Sf?)-!Q-DI-_5QFB({e%D2Q90vam5Q3j{EPo7Wb(851=0P+ z|3EbbmPI-#w|iy1$ScK`f!S7M-}ATmD~yEQQ@f3r8p`Oe94wc94cXCI5XK?Z=|ne( zT}l*Jbh%A@u+QtvihXGvruk9eJ|}xDK6c6>4P|k!vTXqf^d??#bNu9;xdJCWD_|hB z<>uT4=bj=#lr$82)6T^5J6ATiGAcIQGD`F1MZ-6~wOyP_<w*PN1!Xa|tF4w=DVidO zL^~CtYR&+(os#MSg;eyw$cw*emHDTk>-Ex$`bskK3#Y{EkP<X!QRzjXg}Uf{Y=P8^ zfequ+%8N4<Bbvi@hA6D)P)jOxr5Qyh=@dEh9Lk^yGV2aDtD)<&rDQ<PeB4M#&F%?( z4QV!0onrzNtUe2EHR*iDpyTc(Ni*eK{lTU8Pn)Rs{Wnu~DF;>B99Dk&G%ey4`GiO# za6F83yQxl;HXc)4X*)<7V0nUVb=Lrs{{ZxU4n&&JX&YWSvq}1=`x9rXxm(sW|GjU1 zfCQuGqcx<nU6JnJt^<y+1hx9y_U2Gev6YS*%xQ&+Gyh~crv0K`$!hPiO|RhO1Jp$F zi3H3if`;jnuwcOzPeUqXBXO!PN84;g;RD`7L#tDx>-09Wp9EZfx>EhV2i-Fcq-?A` zI@>qd{>U<F$}a$by<(MmsAvGKR;1*h)=HqJsCvUExyEGRwXzIKqsG-u)52C~+{&;R z>WlkZ_~QQPC&!6@TmrwizgLp;{&ps}{C>6xWMewxLpDQy5kBI2<0JlcvzK>l#WlyM zFeofK>nXcvA!;s|B08>PIqWmG;Gai3;y@TozwOnl2Z4Li$7SGuZ1;^gwBl30F8I#> zLI5!TyWG7TYxRr!yX#<ihYAUcWF*o8WF#9hZ^4;^4)g~GA3HNgwKpaYX{sc^?8Uy| zzSm~2C+{WGB{C`z_^<OmzD{4i7Mm3CB(TC+tf{zERM3zi9(_T7Ixb((A9vZ!$KIk| zOqG2~V!haIW}in9h-RkC{6Em2<`?uAM?O*i1^uz+*J;BY)ry$aF%o@2f8kB?T4ELC zh(;<~ol;HJ7J`PkKl6`K5C4eHdI;pn1<j)_fyOtkOG*SgA^!{gm3EAx;>A^_f6;@- zqzZB|lyI4G<xc?j_e#ss5om^C8?|?Ff}~lp2thn3VU_msrV+<hT3wfKiz=5C@5{3& zAbyqpu@iadTsNSwnnULLTq@T&-{ZD<3IW0@^u1})H;2%+@G97$3iZF9_o%C6DF=B` zMMg>hF}+WTWos@f1%Jdj;;l$Vr|?HFN-A*Xe7>1r#q7JDH5!oivg2c)7;%nc;*HI5 zV{|QLA<iJ^xLNhli*xGjL1dn*x1Q!U21!HBchOtOf1hAm3AH$}S*_K^tOrYE_Jy(; zPY6LaAD?lg?e_hC&YCT@g~2GSRm2|MBfMmGs##s4vl&PGCR=$_AAmY^|5b&H6R|=q z20$J*6?44%yjP4lUO@{i`h|NcV0qJ#8CEaub`KGH-|%_nze5Hm34+*PF9xr&HAmS? zs-ubP=Ue%GvTiw4tWVkHxX5H$x_>BrfQeOu%UC4A;M6jMHnE~7mLmez?f6HTf~{xI zu<&~sZQ!`%zk}$g=U(cay~0(F7nLYF3m6FC?<w;I23B+BJ272NFuWy95f6>D0N2Sh zVujcFhvszFUEz&!Nl+i9)-XK17zd~q=*?f=Sz!;aLwz0H*+-!YB9VDlfTUljALmX| z%+xBz3Q-fOHN!A1u;(}v{TIe}O)U=Y8>DaqHjA^-JzI6nPMp!=VSvMgIbz}Eyg8c{ zngT6)DNF=WTYBB(C<T+OHzN%7u3pdC^d475q@s)B$Gcd7XL)9^`$K`dL+<dwixI^0 z@Q)LGS)mQQW@&b?bbqobI`t5`8@LrPW30BV2*|-&aJ_i<ekP4Tn)Wa%5;@|H4g;e= zf(sjvOZ*EN41T}gooaAK#1Lj+g;3spu0B4)ERpm69ktNmK)(~e`5G@cRB!TGn~cX^ zcs^kn1`cq!s@)?G+`%XfC&=>iVir(hEmjtcrrM6MX%?OR4iT(m$7)Z;yb)y0N17hy z^4P=j8b`>R_hnd61_Hs&4bSu`)=&Q6X81w~+rZ5bQY!*F$tBy^$e3>Klb9lv$=%l; zWa*sRpTmlNCw!P5WB97|yx2RPh|}WW1u0MJP<$${my7C-om)?Mrl_*Asry*P&VRze zPmA3v(NYv0%V@uTCC3F{U3U67@!S*MstP!cS(Uh0)lsMfNe`zmDQtGrAK@1fesw~X zf$55IiS@00NMt+Q=b;8Qp&@}J9yq;nF@@cm07e6+0$QYu6*4ncsB1V6&}7C;wgOec zL#)s)m$D1lcqzQej$fF8ePT`DHQEVc#CiHHh<8R2aYMnG6_q(Ize}ySSiHtZrMZ8- z=~*94P$COTq+JS&2t{W&&<*{02bua_vv_M~2YDtVm?TZRzTw%?<I}JLx~Qy-ZaiN! ztYz1XcCq2U#IB$*QAOCvfBlI(5J+@*E6CImvTuNYDv!-Dj2At5>Yh(-MGCT1A<bFT zzNK{c2L>K-&-tY=J~EB(Z(A1@+kn$e==%;<#SJ9$6Rx%K+f5VIXGet#;%HOqU!=PS z1D_I`q`H=7dVOzL{nH=Owb+W^)2c<ZEp2c$_VIf2y64Jk{JP-FR9e+UHCT)~?)Nyw z(FAaaP>(+~Sm_94Elu{DAsDPFZD79St&{XkOyL^p4!J9muULWZI+sTFhRTsX@Dc<G zls3I5M5_+YtIV{Cbk4|6aDtBGo=O$J7?=JqhT>H2J6oQ9M@IF5u(0-Lr(1Q62jGUL z1xg8;0H&lD*{stV|C%fhC9_!YCTOK^1c+fCL%Z^S|AWBXEpPR+$bw50O0`Y18di7B zV~ax~Ll8RjBt6EU3B|nku!OnvgtU1dr}enUm)dx{o+fH2Z*-PDkT;&Gg0+(TB}U-o zbM>=*|IL$qRGdB(>}l^?ZI_7<M-~5mHweAdunB?`Y){$m4-`C0pH>nIu6*Y}X?)iy zvH%>$fquiuLR(uk!}%X#IXSNJ{E2KfigjyMy2Yjqk`Al}H~4dYc>0CV8G49Yx`-^@ zg*$5?9i(%<)$7#(fD?-b>8c0xN)s2$4c&tNz04`{;aRh%vij*pv84I39&^P<JO$P1 z^o!+-X?LVA^*^s(_ENYyPpxrD7`m0AuUbc7&2+J#uufT`BL-W+RHeHnhj{LI61BxB zoBEV3y(_}-ou%$T3PIJL$`50n8n+V@+iTQst+S?8xi%ds53Z25h@v8=#!j@Wr|P)q zdYn+#JCS=>JE%LCu(xG>s_&I63SX}>UVp(0ShwzP6CffExe1Yw`(VnpS)Ry*l#&rx zcN=Y5j{>jGU0?G)P+s=_^YotFB{vod0tAHgYlqbTFZgHvcYoBFnwHawD4Oq=0VXJY zOc@z>Jr0?Z0vjIGYd{{#7*{u4p$`-Xu7U(+(6;!q!_19bJ-}G03F#hA_c*g|^Law$ zFehiB^C~=VsaHTtnOp=-eru-%lXi87LSrsH@9KGWk}nG>i&H=W6AA`4K^>%l$TkW< z1~X~Q<*H_W-Z!)2M2fd7Lek<+x7*+26#sS<;P4dz$mssmC1PH$JL1O;qXKDz!>h<5 zOR=w)mbt5L)97u`7q%Eexyb_G8u-BwUD{B`>8w46(F@&A<te;Vd^<b>qmcOltJcw2 z5*7H47l`IV4!fx9PcdAFB=@zi?a@2u=gJZFc1%;iqfb!yAHLJ^$~=H))xx=}CtFA5 zNkdd+(cE2(t;<|!>6}&t5JUj;xs%Z{ibIzXJ>;jLEnxcVvH!X<TAc+>p8YCDm{4;Y zohtnlD!5#^{;!IWbgD!6F<1nU6CW@ZL;Xgxt&I#yz+B*pvWf_RgH>i)h7QW$#>85U z2AuzQK<P(iR@`lLmYIF!O&5K9lQ#KWHHppo4Ej8yi#TrRZ>K$+HZ5kfVRP!xzqZ%L zH^K0*PhjC!O-wce!6c;~^iIl*+OIEJ`p2Y@Da;!+KS_VPT_(9@0b4G;h~fU$esYHN z@)6`Oy@DZJ^Rz`9`Cq4p=Gmsx39%rE2B)A@Gki#%aXAF$tSl7FnU7Rz=x6k?K`086 zn=SiFeu(U)J*<*Kz3fwGM)64DB_lym5hfRtUC#6^FY<BQvE;s%LQTd_ZqRLk{%FAq zeL2wpOCMExmo%FLOf3TG6Jr}vK&xbzm*svn9=CW(=9J0YBm#_2QCh7oGdAye9|HjK zo3`cgHZte000r`5`-r!xfxm}}+dmmTxNFGV8b0<rHki?}koYmfCZ`(}tmOpzD`1VO zpnI*&p>^;=IP=DJP=gWg<jSz%Ec9}u+6{8=w&^`;4+u^LTB;G=5J=9mcL2ARk1?P2 z-~U2H>QB`?wz$HvS@t$aq1D{vg8OAXoL|rD>;L|d1h624<r##Yo7I}D%rZkmn10dn z)T!)^BX+*7hCe_Xeua4dk_<^%nIqW#+|>>F@LFuCY;Ut)3o{Ak=x&el!Rr8ImvDva zu_Nnn&kdd7a!c&-7XET<=u9Lby>_9ts^Paq1=25ttsCfBf|fCk3}<FGBYU_(BsxX= zL95L+me?!R=YE=Yq$*!P!}+M=vUm1|wF-3vB^N30?5IRs08l#=VX%3YTk;87NxQ)h zN+vbQ{)~cXBs+!NeO`&m3PA$q>0O(uT?+$(#Cv+&2H7Pic7H<ABrq2qcy~rzaT`Nu zBU29OZ)br8FH%Kh_XlZX8zi(?D06y>rS17x)YpdC1XKQa+r_Hsf{8`WhnSDbG%2N( zaK}{@cnh~4d!rV|kd)ITpJ81*Z;a@rLo<FbbIQTBMQ=!1POEXTO)c~fU`#TYYQ8eL z7o#;91CUcCsVXf-uZOCF8-)d+@lyxCd;@<Hwy;E)RmK%vpE`Bf5J5~+!0j2!d+nme zJ)K8d&YIN*PBwd?+)vy@)1-nfY{GJK<SR<I+bt`;lIVzbpRXdk+V2+MS<n9EEOa#} zAf)p|p=c~gb)2E%cTq-;;-r3`+H%ZX5T(Yu<qOI0kayHxXooNL0Es}T$O_omdb=HU zt4N~7EVw&5w10rp8*}`S`kdYTkKNGuuCEWu*YRoc-;U3J{rC0iT6Q~}Xuf(DpF)iQ zm7|gj&N$=}NwFj;0nk1{$)9onYer<Y2vSPtmFIgWYs#&-`X8>YhNV;o+MT!0+R}Ev zfT005ffhT7CHXiwis*PA#^++IF|BeOevQiex93&BHR*M-{(7-IG%<@jf6XN4Ii2)@ z#nx>1pLnei7%v3mFv|t$VuD%L?44ovn1s=%F2-~BQ}?7QN4m2K>|di!SB~IN+jR3_ zTbA!WJLf|7aH8@`#hms^{uyU)oZAbPiA(qPV!x?n8GX7>&c_`O2RMg+B|8A~HOqF| z`-%F2ipdd#+a@m<U9ug2X@vNo3)G;1MT3i9Pa_dQO%@ameeVir05OrQy^IyAyH{yt zy2!5yQN*+61O#pLQM@mw&Z(msjmv@vz1AEGbsM&T5h1ZGS(9s&ho&~xR1P`|Hj@^Z zYqHEC+hq#om;<NLk?MR$@SHf)<ojEYIGt3C{IYylvSQD5b6ovza_k)NZ=9s(&OdL7 z?HKHL{CbsYE35k^&J-xVVMDlPu*ce5*vCznGvcrDIb*)#%vf7pXpi`&RS>{H@B8~T ztynvB>~|e=M|D>ZI2wvmZHS&GoUF<GB_PVspif#u@53}EtO*=3>2G1i;p$OwQ*6^W zcWjCJ!J36ZYhP|VhJaN(5nW3KH~QBWd|v_PGBz+0jUqL*C5{sh$!eF}?{bm8DsqY| z9&@=ecMW?ob7kkFGPF`(N4H^2%6;#<zVBsrfcEzM!K8)rs6rE8oZOD(4dZ!iSnmqT zHx_07E^6Mh1%@)7QK%=oOw`MkjJ~Ds4Q4|E{1v=Er{1sh>%M*Kb{8-V4q76vxx8jn z^SycWzP=N=b=|h>s{h9IgDr+2V)N+l2$G%Tc$>01oJx^Ll!|BYK=6%f3AH5V%Xc;D zq;j}Qw&g2uyO~m{rt)ufnN#vQ$l0GEqDT=ig`|Hq7|G161ggzW*Wy{px0fv^tY}=g zO9#<4k7}mT5k;wTHV^paY}bZpe#yl51W_J3thtG@4I#42z?LFW{IWh!jO7i{Hoxdm z2|fItFo~!QJsk_mv&Ct%f7S1`ju&~J-D*V$c|+v*2~%CNsrVhwm2y;o*iwN+%2lIl z*ZjAh2+c^z=gR8_3t{_@)EAzX7G%N<DJn*+H)}+`uG}ytAd%j(R$BX2>4uY1JNeuo zjCX46Su*ZfKP|Qwcr9VA_c5(;v6Ezv`W*Y}Lib5G>zc0Wj;FH43>Ex@q>v{hE<LH( zn|*v_pB6^QtU@8L$y?vR2vfMVujL`qZEOtCGy2g-lc~~xgN0mauu}QWAJ$@Jy?Bq{ zuvfv7idlem&`dbs8}0?zqqsMl-p|xl0_nBrN+sVEd$S>&42K(WADK++!XU1tO+}84 zj$M7>qbA!eyTaUG)vmD;;Yu$>DTt1uOWHJ)AJNssdJVv}_^RYr1y@q4r1$76)US~L zkK!LQRkk=grJF!}5+a;^?cMfnh`S|TxR{%EW_jEOEz!mkj8s0d6dU3`sXcY&tv?~@ zUX`C_lO@whDK=>$iy(#aW0bzQCO~<e(0S$+nS6sD7i{cvSVY$21q2TJ7A!|eU--1= zS%37W=Qun7Xz!odd;U-zI|7)Rc&ykkaHdt8X`xM-I8%s}&*R)J%*=Y&A$LA6%*6Na zOz--<4I@)Dwaqj(lVy8G-{+r?LTdhe9(=7B`qBLU<s1Beb0csGl({`H5Rgfj|5Atk zYa)zRT~AnHN9(+l-V|8Y9V#fd5l<XHqs?&Wq8$VIyR;kvh*^X#ucY%Z7E0+<$RTA= zeP1FnNGN5{_jaCen|Pjx^?p2Nlj<g@0acm_!e0#miBGF!T7KB7T+{Xo-kN>?I!R^n z^MuRgBqGE$Kx{=L38Y7ufi9UOvho<{uAxSvaIMtmX{Lzl*7nhvya!vdi|5W+v_0T7 zt6B@yMO-9!ZbBLu$ssb!je%HG?x_w<czy3%j7g&8NKNAQy5jY#B(a8bL0m!*hF;*8 zmw{v>3JUK**SS!eLdXP;mE^HRY5IYIH*t=`2;f4QMa50X9VvrNuWi31l^9RExhSNd z2_WR~Y?gwM^g8;xYJcU^@jb6oo|=pvH|83pmHV34qEY~y5-Hp1znkQ9p=>RV#Bb{d zU$IjczPJp|=~2HY|K<C8&4jQhF7)CvYJz#Eg1A@|;J99WHN-B$Otc{_marCJwuPva z`GeV|;x7lxTWI#phf@ax%@v=y<YI$W1$1O%<cw1b=;evXcWtS2Ckqux9S(0gIqwl~ zFuuE<5Wf1pYQ}VoM)P7Wy6PiMQZ5tictF{slhrCMMH8P397D{rcigsgM<M#l=#>Qz zw3uod5sD1p{lkVYA`RbuW<qF2bEKioFD<qY&7IWmjS<$W%N2iwD{a|s9e4M+WU7o- zm@-QNP!4!m&8mE!TPPOgVOi8t0Q?m5nNB!6Qr#&hEWDmAS!N(jIv^f*pp7MPxK`U* zbx7!V{>?zKIB)R6%2&N>Ot9&X-UOoN3srPq2JSQBII<^MCm`+yq0SQd9AmQiiGCXW zxx#%H?Mc0nFnQO2y}iXB3SE(t|5Ui#g>;M<fxg7^eDn6WAB3H?#k*62qId4S9JyXR z$Q&``$`f~k&_+OSEHXgG<boJ3qwo-&rp)S&s`YeS-=n>{Z*-MhwIYx1wkP*4W@o6A zg;upv=`))ZRyzxd7FCM6+JeK;`G$FyfqY~%Sa^@2mTx2Mkjek$dE_~z(XHvHN36>} zC|jsnxDo;LD$Jm5iE;jGn|M9-Qd=EpkK(Sh2`jp>h8d(9s}AsSnYWC+YR%qiw1QtP z6J=366%(H87=Fu~rd7in1l^x^B?%8e3rMJfCL?j-Onz%J{G(S)*-D5!E<cQj9G%L0 zWC=c5(kxRbmaieq5)T(YW*#?XHxAvBDf~?SobwY82(MmWfhZDSiU$9j+Drdicq<R0 zhahu?7q%3(CO_n<mmqGfwEPD&iW9>0J0EiUhu8tVe!G=E7TIxy`$Q)xpN=qZSrzW! z8%^6M(tkP|iW6A@EMIUs68?WV8~#<woN0_Zu5zMw+Rb+c4QR0?;faCrgAy?-6roAP zsG$GUmAGCirWOgMQ>9WAnaOs~w%XL!v&ID{{>D7cnZ3SxLX_HN>gM-;>yqgEdfMc& z)|#q8P$;q4tFzZ&Tq|9bzt?!Vj?PgfRCRya2K+ofKPF)?W|vMBq!8H%Tuo@dtcWzc zEm|B+{n4Vkx9(CuHpA0F-8$;LE@fw%S9rEAmfJ3dOwn;qBFW=(nQ=C~ek@9n`$#dq z5JhrXv}{vom_&cMppTJ6v@mMiPSv&Ujc#+JxSHWzr#{lzG;B;?=XKoS^;3(Eow@3x zks`lStYhKL08Ejv_Re;<;g@JcCOQ&RCh|c;Og?9$@5FwS;p6T4>fh+y1&z(0u?bRD zE`J5dg`so7$%zZcD0L^}TWJc#F^PM6iMoE7jbSdOywJ7Ms}^FNym%2--D5|O+gyLg zUayRlB#7#Io*VL$PSvtgz+^cDo1TKy9Q3Jj-a~dNwUOPdG{~h#&0He1-mKzXRbv#l zH7Z?ixF;B#77ar_CsZ3a+btc-=i9gN)X9h8dE*}9a4Zel=~LAKpjIT90F+Y?<f|OL zoxmOiVa}z-jNvg)e#cz?MHbWBF~Fk(svarB^JT?O9_)@j+a=by4zhE6s`6w(+J-5x zsih2fxTSwwx9!$0{O!HPafI1ldDOnXn9o)Kv`StNJ7}TbQ;mRRoVYp{yyK6{tso=0 z)F=^TMN<;W9vqa5menCmQOhXWAudvR{^in1FA7(tCE{-ofj`t-nyW#?$6H*a)vg{@ zEe&n%$QfhclUX&{Cn&uHOrZSo{QWabbLr&GpjA~`1!G{+;aV!8(H|>-%deBpcRGLz z_cz1pk-6eKFaFrU?(y=L^V$U-emj?c)kVPfEx!$c(qv=jnxVXeSIKw8efU|<ans1K zeX^F6O-UHhQo<#dZ)}PDFFC{>@cmCZE+)8aX{%qI<|AG=s%g-c`#VG$=ZG532!bOa zCKB_{WTGq;8B6kG;eiZ~)`*n-!YHcGcW+ORd7a7yzV{%=;M6|WS(`lZPZnwPH0r68 z6U?zXI9|r>v9XXS5$|GZSf`&`hZ?OLM~?D?yi*20aQZ?{+B8LaIXl|upAFb^KGG)Q z$5J)w6>M|?p0#g}K-6U#I%mJR;Ie4V3bHGiQ#sS!<2%Ev>)U3Zw`1##Uunau%8kF| z5V^BReVDcTrc-GHJ^je8b*ejPxEMFxG|HZkwE?Jw7S5^pk&F*we|x(dXlupVySnbG zdENP}CjHN(V2_*;5g<AgF++1qA0TAdWtH?|2=vigkGx_<>vm(MZd~;%qXW$lf~Dk@ z)4|U9Ep61QNQ&uhCWt*mbibW{+s_hS+}Gy}M9rj*OwWR!7__uay9RMCbd-#Z1G^kq znw{03)P0oHSW~dKn1STbCL?S_Fwv`iv7q6wASlI=GhT=^0r<-%NUFR2Q6Y;>5m0}+ zV^;qfOM75@CkdH#q*oNsw%!%n1mp877@8{W#+W3X1)5q>tElbo*(L}t{Ro}T({XTx zuUJaj`+izjhM%8*)2FZ_eef>o6sU*`@21KzkpBzu;i?h(6WHdIUG%8vH2mwK>Lfw- zFkeb0;{+N*mTFxrimGq-l$8!cPRB~u=o83s*8ItEj<68-Of>uT$)Kv(TEH(F54JSQ z2#8%imnuBIWCP=^x63r|`42oAUALt;2$k8)Ir-KOZEYnK>+P2^Cgn7C*_KV!`zhF- z#6rJd{t!Y2vbdWzsNjOa3KH~u2r=;hcKu<=>sHn4v$oe$A8+K0BIYz;1!))&z)_NJ zbLR{f3MiyRO>?(hghmAWs0Y>#sn8~Rk6$H=CXDjUn&*WWDWX}xV~>50tjF^qdB=y| zKgn-Gp+AssAbA<Y2<C{wc<TJah#w;uK!NOsYe<4)LiZHGJ8_&IO9$=UMnRdOco5%s zmcX%SqM6$gnz$&bh=N~~i#Ife5gZ$DA;O}Q?&mX|G=nk?W))e<(zxpF%Cdyin?cN= zlZYLJ;q7HPzX!gA8)7`!+QZw^GqP4~p{)dvs11Oa`P1<KDrPmF8kL>J+9GBJt;gVm zE1hAeOn0cmS&I{yRS4(TJ*UN53p9Y8X4;f3g0~+5hv|u9Bw=(^AMV2F6GO(kJ)ip> zfN$S-L?0Rx9jYVRflW~r0K?P#!)gGpACf|-2Py^DzW^Cd=rCZli|nsRr#!W5voE-L zA|C~Ct%Bq8m6f0qi;H}0Fe!{=>zC6^lgI|LKu|k%AVvSAzyvZSF%sxDTn`g<R4vzG z9wZm++}g_ZAEhRWW0}N3{=un2-!3qH-uc9sY&z$-YV#WDg%9u`S3gg*^l~MD81ms{ ziJNs5gQ*;U-wrV?D;-W4y!#mTKxdUIEFb(Ip+`s_LH=0`D5YX<cyB$B-_|ZImxF+@ z)e6Hd)TX{sS4&yeO0CGUK4oL<xdg2CE-^9?*O|4^H4)>el$1V%Z-Ak(hZTPj9_W7C z!5gbYgzgg8ddJ))Pk7H@L3j3^XxIP<5|VNohX&VlUW6|_(54vDXXh2lnM@KK1u`LU zzy#q^Y^6rI4Qm;W7@!4{V==t`>v~tE54*BRyW*~N`Z%siBKHb=OejW(5N1gaA9eBB zt4CxOw^bAWl|pX`A9-?#)qcI~dIo*Nz2q-9LHbC!D{})BHMb+q3Uc)o7cJA3CmID4 zENeH`JfsEw4|Y|lb<1nT%$2E&2DvKl7O+^xgPed^q_t1W&eyL><3F?^AD;ksRCo{& zIR5`qYW!=p+0$D8M~MDU8xk-)p`1Xf^!B;@yN*<<wxZMoke9inhOZe&UP&!DN=UgO zYv#Esc4SkPp3xb+Ss*t9=AXnLuWBr|5rMBu#RP+w>16)_;_d!=Tl|M%Etm1ezFI9_ zq?9CAKX_1=aT!2%JzuGU;_Lf3$k%?}q7dDKfoO=<7Z0)?XkNwqd-pl^w<v3R-XGiu z7CW>ZXubX&=t-QXlM&p%1HYmAa3jfn{Qh$afxhrKSUrV1sQ}_!WzXrfE8e$FMm{}| zZw%QBiZW~-JmhAi&yntV8?N}ul-@>Kul6U5N^l#YtljHWdYvnB#xKOyt13?|c>5er z6F-#mk6N%11HY{IX)kUA37&f=@XadxSAP_6qr--H6I1qq#)pBk3i>i4+q!p#d|p*X zf|*VU0Bz=#tXnlR#MZggd<|uXCud<LQBNl`z26T)UJx6{@n-;+gKr=o;xM6PIkNm{ zRDk)Vr&~9qwuUr<n*sWC!dPQ7>$+hDh%N2im$pAl@U)R>p`_`P71IwS$Q^n`xW8%+ zp1D$bW4o!jupZNqNK%RNv#$Q#5jl&^9u_nqkj%*+{w@v<aLwF)mTxb)_6BA0rQc5u zLp^noE9;$jz7lHU<talf4o}mnm`JJ$f1&AKbN*p^@$C`(0lFby_VLO*EgoPNU5+}6 z8&pum6Rl0tcOnfs-p-VC0;`#f3+laP6TrB^TXNwRc32!l!xqN<+)*;W|5B;FYVeyY z6=#c#D8AHOdTu@I_$i;IpYYi1Pfqges9yLbCLdl~cfo4WWmV65&^L4-uJrQ+(TxiZ z%SO=YZ4TaEzJj)G^7;_W+Vd0wC6$Xxr`aLoj>hYZ-Sfr~cXAljUi~JQL8F7>xXe1r z&7ly_qiFrL7sHE{pS>?}Oi`U0A69}THP1ap_GV}lR?U-CNsJ{ZOrIbF+?*g>m=oTN zKMkzlcToIuEEo$~{HWk+FhjwiLIK8LPW*u;WMil&Qjgha60{t$Iw`cCQCjs*w|6op zRgz^G3`C+@%Fa=z6{KtHiMw9lFIR?mIdX_rw;Unb=+DPkKaX$I>8By#c2Xs+BB+Rn zyL|atlEIEcj~ePi^bgKp2>|-hym>D)Pqk?EN~zV~IzwcO=9#i2W|a`TJDm;Mgb+p? z+7Fig?3gqz`kae?j#(tcp!=R*TKTP>xt=Dw?5yRru$3TX%66X);fysoo*dE5ir1%) zb)eCum;5%!?(&1pl!FhnhPJ&&#KNLe(B24szr+)o#z9vHd`JANKIO$*y8Erl==(w< z+}@f4v*%0n?_e(MVJ~xI?`%nz-c6zW?e)VS;>*>dekFr^auv)<Hu>ggo=%E^wtU`> zeIX7vyvt`Wk$gK>FMV0@bcr^Y7w#0%@gR`6#2g;_o|0U<UOuM^4XbuBgVpAdja4HQ zg;Bw_Ich_Y8XwqY(-QPxXuY<(_Ye2b%6>A)H6zfJ(-&?=>EqX9aIMu&xPlP(;FX<3 zGHS>4*yB-#jipyNbSV`x*D8eUc-dXga#v@}1)^}Ttc+c~CvRq0-!$FXr9c@vU3TKE zA8dG88j3E~{lLl;Ig9*j*2p?v-)_(|RzTG*t@WDlt}kg`AJZ!+<vtk)Mmc6wKdoO? zJs#xyef(=VBbT#@G3|l(IQ3zsoMb<5wJP_$+;YsGwpTAEm7|L7JHIIrN@r?u-ugx& z{LJ-w%)vG$;+*5pIr#Mu+1M!H@hN@pwHLbUdbCai7317wS5AjH1wvr#T$@hlRlX<J z0~I=9R;J~N{AI@SO8M*dNrszp)*Q%>k-6=^IEpE^%Xy=%zSgDutiT?T*J8A=`FizR zUyLfzB%oeScI-gu1};h>GTS`~;~0cNJz|)gEye48saJlN^lDUWnON@q%bOHW(0nMn zF$uB;zRk?*5|(dy1sAwVgYdU@qHQhG=x{PUqGgkinY}p3=ddIx6?jf4o?g~?zOK5e z?A9>R*=beUSZ{^h+PXPyBQYHfR1)Ik0po<^_<M>4g@Bh2gO{%)^aGzOERw2FrR*4O zKT(NY<kdc?V5u-{w{(H@w=2Lu;y9~uGG;Q9Mx<-bM9R_)(aLKtb;}d0wdn8MrHzG| z40RE-ChCxa(`Kn-2X4izv%ls!!uvunJ2*gPRibVF#3JYJGE``2ilSWV!MFKH69DFE z9TwBiwh`c9p~a2N!T8+OR~jFd^L+b2rw{SXp^m^w6~y?Om7W>rak~|^qaUvGlgqo9 z=Lii;nXU);u{X#k(STq0P;|CS_7M@OvpQ{Qr{4&2=)CuvdINwc(QEO*t#Vkhe2F|H z2f1E0uLrE0Kk?KVIo>7N6={jZyf&ocxWaytXh49!4s2Z8vWHouegtAf8j<OH!ji6a zL6(8I+`#ucXT=_H-V8lU`wMo93Bt#*dkXdoj{E3hFlsBDnD=(TLjURb-g?ORyn5Sw z@7i})8|iN4WdI|JvcXI_ArLS!nr@1^E0m<kT;=!+2mM;%bdKFch!a|GFHnW8p*j}3 z@(9X?mUP+V3vfEQ)~JGI?AAT>xov*&+{D+B1A)bjwt!;fG9;)aneQf9%`zDUl7T4X zo2!E+6#lkyQL36xt&f3OfTy~;ncm5MwnL$SE;REK*TmeT-E#d6-!%H=S=x48%<yLs zp~ARVn=?_P`-WU}oLTkz3H!IBQ(n?+Zlmy)A|BJYuRjq*Q5=dv^q!=FC<nR0aS^P# zfz*uj_>^;9^gtlWXc9Tt)oiT=Rph{=G6CvK!_${7Nm?TysWQDRZ;3|^PS=qQLWWA3 zBpYzP9YlzLi=X==@G`ihk=2x2Ak1uBpe;Ri5-y1x-Q;?4mnn6bK=zxcxHMURiDUDa zP%tr8?}Z$(ZMObSvj{?teqaED7m$DY0CWw8B7C9tfmOI(C{^enfd+l&8RVJQJ18`* zh8(lUW1^J>Ny&JWU3qu`T_@3ITvs%bT%*TJRCPI+7T-eSnfbt-#xg)s)_Y(Lv(!98 zKE5w3vIKMJm%LLUHK!fx+nM*QC}~8fF;y+N0OSR;c+nrgZIhWuf_SC7dIH&L0?BEu z?G?&N&2G}tvgj^9&OA|dS!;zwk<AoQtL0?Ho+IIV%CkrWU1TJ628}pbX{c>lktRjF z@D1!5%M`jyopt|E#JVvGzw$N#jP_6fq0YPvms!lK1+E6uR-5%`N;yuW>2*-U4ErgT zrI|C?viv{m^m@rY>ENI}rlUSU;l4-ws+HLzZE|fE<j9i#P@y>DhGY>SPu}BQZKPFM zkY*{4mX;R}Z-j)MMI#BdxiKmX^wprxM}{JsR;GoZC+UvXi1ToAX2D%I7e(lg;}?Tn z^ADg_aw>|49l6aMZZLha{s{MU7E0Y71i@V>0aiBY)Co_?w0Dk4)heftAK}E*!6e^e zq{1=&*@D!5C-GdQ(+YWtyG@Y3VMbC%&y*8fAnotu%b^AH3rd-KE99iY9<B{Z%B+UD zb4lG%cR<k4f(%<kXLRg*7+tI{WeBVZAmta{9ghUmyTH+xE!V>Q6fT1J&=a-SP+s8< zX5E2|%sV(BQF9FnNkqjwfzx84RoAJ`@ak{HwcQE8L#82DTA0sN+b^@%mn9XzF0)!q zYEX>COJvu^OJbD@q{_~3Q4A3}2Fox^K*)yfmfT;a)yb*8_|x`-@mqp5>H}M?<W?Jl z$9BWVo(d<^&HfD9#UDZR(G&9}EMh68+74=N3Gu}O`xkcp?<MWao{6+F=n_?Cs`W&w zskeX$vBj6*9fPA7-sa(H80}_oR@0U+BUZgDhQ)q3TXUpvymrIXag5Q!`sGN(Bga-g z&T}=rM^wItrP2|rW(3M%xNfPEee`N;5PIGL!c-by4ny;-yIq}G1LVkMT<R(+re3Pr zoU!e?O*_eVh!wF!{jo_hppjCeOHDgzDg)q|x7@C^JoI$)EWo&8I7LO3D1zN9*L99K zc)5POHCR<U<9sAf#8R|m-9~*wBYl3kP^z6)#!TVtuMj%isZ1EjY`bi@A#q1UWv6h1 zdcLio%Q!rm60g*=J<6}q_fgDe-1p0m|2VYtJb+pyM+X6si2N^|#J{GjJ)Kp@-DcN+ zJ=n9wG_{__e~4go526o~=$=)t=~7yYU#Uf;>c?jjN`w?Hjtdl%7rg~_Z<Bn7<(}kj z$Tf&WzI6X&f(ewhgG&1Zqh##WHPtl?a95Ll&lQVwyf4#?(66s1s<R{Hz)i%T0-wG> zp2$nrEXQL8;M@D=yZ1JN5nox@`_koLFRJb-(&H$S1J9F%o!ye}mDfh~0^EuMC;a7( z=FhY95H~l0)lDz>si*8pCZDQ9h<C@A3%TjB)$Oj!{Pi}*6OZd^4AIA33LFEkxgd4c zJPoh-&3i*<w<l!yPzftO1Ss9rO$H<qzaz0&-{;hv45W=N!kACGEVhTyc5O=^+^y0? zDm;Xd!rR<Sj$Jz)Z^I<1L4l^9@1^^n7;?TZ`-iQY=PC4$ql*HcN-cK^N5c(xmDrUA zR$qH5O<V2^fZh%Y8>75$Emyrj$gC$~4^2QzOsNw)3O}pQ3DhH!!}j20??b_%=D4#< z(QRp5`TNYZFW2#aSH-}7KtWZpk;a?nO4Z>d;oW7M?}|pt@~$nTcta`gr#r3G$I}2I z<mZlFFn(U)g55Ruu9Tjf$%Q4GDd$Zpob_31hFC?=!ua%cj5>(I68?h!iD}(^smiN% z{A<L(UW@T$XTkve1ihQ4SdJzzn@zL$X9bjASB0WXa@FF~Eu_<I#cB%!t(>!5y{o6Q ztK-T@I=~d)V?&()9>?z?nVo~6@Ai*yh6iI@YmX8u@)aC5T;8tP;Lk9#3OO~7pD=i3 zJFkb5$trFd`#vw#^#!&)3VP~A8jR$l9nVwGE8fsR3ZUstx%T>pO8NV;UZ&UkYn|xL zvrfI2cOv{}`6m%Dn%EC}H9A)P<)$j#chPQ;m1Z(@ypzM+W4eP+x9lcEEVkP0_o{6N zZapx1kQB|)#mD)N;jSYkhfA@q+Y~I6z)z1SBgVJG(+s&BtMjl8B)?kLUZ%Z;)9o!S z)*6(=9Lb;Ok`h=uGa{xXQumBs?lVJqtHPOY@H#g;)vS$$S?T7@5e7Yb6rzlQSus0y zjI|uwYS+Wv_D6%M`5P64+r4AzzIUzp)-{pwI-!L|>JJKwiC&)gAsqr`gwMN9mkXB^ zq#|u{oB7pq&zpXqlerA_2N3ZLd_OerO*70x72aY$nhN3z!ft+kXWg@&)X+OqX-aB% zITGvESy8`84#o6ZlIVQOx+asf&-#{n*Vejt|BBC$-q`?UBdfd;%vtri!+kF$M8)BL zd(ww0i`?nqPH+#Y^F+||GyeJ>gUN#vwl(5@{RxN9`=yHWiL~U30^TcQP=EHc$5>)u zpDypR=QQ@B-IXU&*D#gq!8SkloSI=LANV`4+iB1C^&pV9iNzB57IU5oDX5rhiLF<x z*m3kYWqx<}j;O_M=sb`9$=#4H*szVXs=$a$e{?WjJm+4@@<M9xFcS+5olcdiqEw{F za9!)wE!|B8S^9hV8?C9>VREmjalI^ChtQcDIz2Q)ZC_xfQjfbW1zk};)i1=-I>z?Z zH`;nPb^H{i;=^VSP8qRx+Wrv<Y&=Mo;V6gL3Aa1QGobL71$V>@FEJg~fQ91rIVt<^ zncg-lGCW=VYrW8pRjO}vH>}9H2ZumGIC&(m4h6n&Alt4u*(-Y|%RFIw@#irmjmRI+ z+IEGvyZ7@SuF(*~o6GICg#(}RZJ+%D@BK}FL+-Cc6?%S4JT!bIG#6^k{k@#y*piTL zt3Fe3Kul2*7NCLN$Ih`AgR)#3P?1jeZ)ZU1<t7L4@4-7N%+LDQyG`CRek#i+AVRAF zV^jm^#ru!>%Ti1i9EGbR4uzR@9`#d#C6I5f&M4z<fphzWB9TS6$tR85NOAvzuXhR( z9bnpZ$F^<Twr$(?8qch;ZQHhO+qP}vtnWK}@2kK5+f*vmNq4%ExABBYF(qKP0#Qiu z2F&G`Nxa|Nw&qG=w(mTjfZWUf5-xOG){7(XWmIE(I7?fnD?KihC>i8WDn*<zs2BZo zOQ!~SBswPm?WqPKC7^$5#8O)`Sbo7$J2+<onAX6-D*_9O*^+*b+xF_B;(ct_`SRNY zXYT6Oje#rPJ^e++8mQD5@iMLvk9oQW?BY6el6eD=1`-oh0F(S?qJw`*SDjkK393%B zw~uT^cFim`(f@hA(_n@z+`n1DZmGA%!+p>lb!gTdJs8<XbYtCa`k~d7CdcmsNmv;w zh!F-p{o%#H8J8j@y_DW0V&x@EJ5I_*m?j}K-pJxgwr3o-bpv;<imV(7eT1LR4)Rtt zjalK~D!+=n4Nyu!>U?>+*dpJ)nh%-@AqU;O-m7aT?(SCa#eyzV@saUy^D_sLt$XnK z(kkZiG5>nhQ)WrzN$5%VyOYtSRUXjx#urb@`-SI}XpStHU~nJl%z=7OI=fOj!ak=W z_@X7_mbDKwVe37!ktR-=<<a?~>GO;1ll$aPp4F7Cd9u&%e8_K|{OWluev$fmpPOi? zzLS}Z�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≻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 literal 0 HcmV?d00001 diff --git a/libs/community/tests/unit_tests/document_loaders/parsers/test_public_api.py b/libs/community/tests/unit_tests/document_loaders/parsers/test_public_api.py index 3dc19adb31076..efaf28ecc89ce 100644 --- a/libs/community/tests/unit_tests/document_loaders/parsers/test_public_api.py +++ b/libs/community/tests/unit_tests/document_loaders/parsers/test_public_api.py @@ -15,4 +15,5 @@ def test_parsers_public_api_correct() -> None: "PyMuPDFParser", "PyPDFium2Parser", "PDFPlumberParser", + "VsdxParser", } diff --git a/libs/community/tests/unit_tests/document_loaders/parsers/test_vsdx_parser.py b/libs/community/tests/unit_tests/document_loaders/parsers/test_vsdx_parser.py new file mode 100644 index 0000000000000..70cbba035154b --- /dev/null +++ b/libs/community/tests/unit_tests/document_loaders/parsers/test_vsdx_parser.py @@ -0,0 +1,51 @@ +"""Tests for the VSDX parsers.""" +from pathlib import Path +from typing import Iterator + +import pytest + +from langchain_community.document_loaders.base import BaseBlobParser +from langchain_community.document_loaders.blob_loaders import Blob +from langchain_community.document_loaders.parsers import VsdxParser + +_THIS_DIR = Path(__file__).parents[3] + +_EXAMPLES_DIR = _THIS_DIR / "examples" + +# Paths to test VSDX file +FAKE_FILE = _EXAMPLES_DIR / "fake.vsdx" + + +def _assert_with_parser(parser: BaseBlobParser, splits_by_page: bool = True) -> None: + """Standard tests to verify that the given parser works. + + Args: + parser (BaseBlobParser): The parser to test. + splits_by_page (bool): Whether the parser splits by page or not by default. + """ + + blob = Blob.from_path(FAKE_FILE) + doc_generator = parser.lazy_parse(blob) + assert isinstance(doc_generator, Iterator) + docs = list(doc_generator) + + if splits_by_page: + assert len(docs) == 14 + else: + assert len(docs) == 1 + # Test is imprecise since the parsers yield different parse information depending + # on configuration. Each parser seems to yield a slightly different result + # for this page! + assert "This is a title" in docs[0].page_content + metadata = docs[0].metadata + + assert metadata["source"] == str(FAKE_FILE) + + if splits_by_page: + assert int(metadata["page"]) == 0 + + +@pytest.mark.requires("xmltodict") +def test_vsdx_parser() -> None: + """Test the VSDX parser.""" + _assert_with_parser(VsdxParser()) diff --git a/libs/community/tests/unit_tests/document_loaders/test_imports.py b/libs/community/tests/unit_tests/document_loaders/test_imports.py index 43758fc16540d..e50342a9a0a20 100644 --- a/libs/community/tests/unit_tests/document_loaders/test_imports.py +++ b/libs/community/tests/unit_tests/document_loaders/test_imports.py @@ -165,6 +165,7 @@ "UnstructuredURLLoader", "UnstructuredWordDocumentLoader", "UnstructuredXMLLoader", + "VsdxLoader", "WeatherDataLoader", "WebBaseLoader", "WhatsAppChatLoader", From 226fe645f15980410306f934b5c8966d30685ef6 Mon Sep 17 00:00:00 2001 From: Nuno Campos <nuno@langchain.dev> Date: Mon, 22 Jan 2024 22:10:03 -0800 Subject: [PATCH 179/215] core[patch] Do not try to access attribute of None (#16321) --- libs/core/langchain_core/runnables/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/core/langchain_core/runnables/utils.py b/libs/core/langchain_core/runnables/utils.py index 992ea077bf449..03edb0c291432 100644 --- a/libs/core/langchain_core/runnables/utils.py +++ b/libs/core/langchain_core/runnables/utils.py @@ -248,7 +248,12 @@ def get_function_nonlocals(func: Callable) -> List[Any]: if "." in kk and kk.startswith(k): vv = v for part in kk.split(".")[1:]: - vv = getattr(vv, part) + if vv is None: + break + else: + vv = getattr(vv, part) + else: + values.append(vv) values.append(vv) return values except (SyntaxError, TypeError, OSError): From 5de59f92363e1b0c02152df8e91af4bbbb185861 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Tue, 23 Jan 2024 07:54:47 -0800 Subject: [PATCH 180/215] Core[Patch] Parse tool input after on_start (#16430) For tracing, if a validation error occurs, currently it is attributed to the previous step of the chain. It would be nice to have the on_start and on_error callbacks called for tools when there is a validation error that occurs to more easily attribute the root-cause --- libs/core/langchain_core/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/core/langchain_core/tools.py b/libs/core/langchain_core/tools.py index b83a5cc8a7b95..a536da4c79121 100644 --- a/libs/core/langchain_core/tools.py +++ b/libs/core/langchain_core/tools.py @@ -312,7 +312,6 @@ def run( **kwargs: Any, ) -> Any: """Run the tool.""" - parsed_input = self._parse_input(tool_input) if not self.verbose and verbose is not None: verbose_ = verbose else: @@ -341,6 +340,7 @@ def run( **kwargs, ) try: + parsed_input = self._parse_input(tool_input) tool_args, tool_kwargs = self._to_args_and_kwargs(parsed_input) observation = ( self._run(*tool_args, run_manager=run_manager, **tool_kwargs) @@ -392,7 +392,6 @@ async def arun( **kwargs: Any, ) -> Any: """Run the tool asynchronously.""" - parsed_input = self._parse_input(tool_input) if not self.verbose and verbose is not None: verbose_ = verbose else: @@ -416,6 +415,7 @@ async def arun( **kwargs, ) try: + parsed_input = self._parse_input(tool_input) # We then call the tool on the tool input to get an observation tool_args, tool_kwargs = self._to_args_and_kwargs(parsed_input) observation = ( From d0a8082188388569e88aba4120fb0357c8e16ed4 Mon Sep 17 00:00:00 2001 From: Tomaz Bratanic <bratanic.tomaz@gmail.com> Date: Tue, 23 Jan 2024 16:56:28 +0100 Subject: [PATCH 181/215] Fix neo4j sanitize (#16439) Fix the sanitization bug and add an integration test --- .../langchain_community/graphs/neo4j_graph.py | 2 +- .../integration_tests/graphs/test_neo4j.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/graphs/neo4j_graph.py b/libs/community/langchain_community/graphs/neo4j_graph.py index 92efd27f0813c..b8970c06ecce5 100644 --- a/libs/community/langchain_community/graphs/neo4j_graph.py +++ b/libs/community/langchain_community/graphs/neo4j_graph.py @@ -160,7 +160,7 @@ def query(self, query: str, params: dict = {}) -> List[Dict[str, Any]]: data = session.run(Query(text=query, timeout=self.timeout), params) json_data = [r.data() for r in data] if self.sanitize: - json_data = value_sanitize(json_data) + json_data = [value_sanitize(el) for el in json_data] return json_data except CypherSyntaxError as e: raise ValueError(f"Generated Cypher Statement is not valid\n{e}") diff --git a/libs/community/tests/integration_tests/graphs/test_neo4j.py b/libs/community/tests/integration_tests/graphs/test_neo4j.py index 948fe4c7190ac..e4b47f907ead8 100644 --- a/libs/community/tests/integration_tests/graphs/test_neo4j.py +++ b/libs/community/tests/integration_tests/graphs/test_neo4j.py @@ -88,3 +88,31 @@ def test_neo4j_timeout() -> None: e.code == "Neo.ClientError.Transaction.TransactionTimedOutClientConfiguration" ) + + +def test_neo4j_sanitize_values() -> None: + """Test that neo4j uses the timeout correctly.""" + url = os.environ.get("NEO4J_URI") + username = os.environ.get("NEO4J_USERNAME") + password = os.environ.get("NEO4J_PASSWORD") + assert url is not None + assert username is not None + assert password is not None + + graph = Neo4jGraph(url=url, username=username, password=password, sanitize=True) + # Delete all nodes in the graph + graph.query("MATCH (n) DETACH DELETE n") + # Create two nodes and a relationship + graph.query( + """ + CREATE (la:LabelA {property_a: 'a'}) + CREATE (lb:LabelB) + CREATE (lc:LabelC) + MERGE (la)-[:REL_TYPE]-> (lb) + MERGE (la)-[:REL_TYPE {rel_prop: 'abc'}]-> (lc) + """ + ) + graph.refresh_schema() + + output = graph.query("RETURN range(0,130,1) AS result") + assert output == [{}] From 39d1cbfecf8ef57822f7ff60d7b1bd19f60f3180 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Tue, 23 Jan 2024 12:32:45 -0500 Subject: [PATCH 182/215] Docs: Document astream_events API (#16300) Document astream events API --- docs/docs/expression_language/interface.ipynb | 732 ++++++++++++------ 1 file changed, 485 insertions(+), 247 deletions(-) diff --git a/docs/docs/expression_language/interface.ipynb b/docs/docs/expression_language/interface.ipynb index ffc9225ac41b5..6837a73532a41 100644 --- a/docs/docs/expression_language/interface.ipynb +++ b/docs/docs/expression_language/interface.ipynb @@ -30,6 +30,7 @@ "- [`ainvoke`](#async-invoke): call the chain on an input async\n", "- [`abatch`](#async-batch): call the chain on a list of inputs async\n", "- [`astream_log`](#async-stream-intermediate-steps): stream back intermediate steps as they happen, in addition to the final response\n", + "- [`astream_events`](#async-stream-events): **beta** stream events as they happen in the chain (introduced in `langchain-core` 0.2.0)\n", "\n", "The **input type** and **output type** varies by component:\n", "\n", @@ -87,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 2, "id": "25e146d4-60da-40a2-9026-b5dfee106a3f", "metadata": {}, "outputs": [ @@ -99,7 +100,7 @@ " 'properties': {'topic': {'title': 'Topic', 'type': 'string'}}}" ] }, - "execution_count": 13, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -111,7 +112,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 3, "id": "ad130546-4c14-4f6c-95af-c56ea19b12ac", "metadata": {}, "outputs": [ @@ -123,7 +124,7 @@ " 'properties': {'topic': {'title': 'Topic', 'type': 'string'}}}" ] }, - "execution_count": 16, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -134,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 4, "id": "49d34744-d6db-4fdf-a0d6-261522b7f251", "metadata": {}, "outputs": [ @@ -150,7 +151,8 @@ " {'$ref': '#/definitions/HumanMessage'},\n", " {'$ref': '#/definitions/ChatMessage'},\n", " {'$ref': '#/definitions/SystemMessage'},\n", - " {'$ref': '#/definitions/FunctionMessage'}]}}],\n", + " {'$ref': '#/definitions/FunctionMessage'},\n", + " {'$ref': '#/definitions/ToolMessage'}]}}],\n", " 'definitions': {'StringPromptValue': {'title': 'StringPromptValue',\n", " 'description': 'String prompt value.',\n", " 'type': 'object',\n", @@ -163,7 +165,10 @@ " 'AIMessage': {'title': 'AIMessage',\n", " 'description': 'A Message from an AI.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'ai',\n", @@ -174,7 +179,10 @@ " 'HumanMessage': {'title': 'HumanMessage',\n", " 'description': 'A Message from a human.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'human',\n", @@ -185,7 +193,10 @@ " 'ChatMessage': {'title': 'ChatMessage',\n", " 'description': 'A Message that can be assigned an arbitrary speaker (i.e. role).',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'chat',\n", @@ -196,7 +207,10 @@ " 'SystemMessage': {'title': 'SystemMessage',\n", " 'description': 'A Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'system',\n", @@ -206,7 +220,10 @@ " 'FunctionMessage': {'title': 'FunctionMessage',\n", " 'description': 'A Message for passing the result of executing a function back to a model.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'function',\n", @@ -214,6 +231,20 @@ " 'type': 'string'},\n", " 'name': {'title': 'Name', 'type': 'string'}},\n", " 'required': ['content', 'name']},\n", + " 'ToolMessage': {'title': 'ToolMessage',\n", + " 'description': 'A Message for passing the result of executing a tool back to a model.',\n", + " 'type': 'object',\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", + " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", + " 'type': {'title': 'Type',\n", + " 'default': 'tool',\n", + " 'enum': ['tool'],\n", + " 'type': 'string'},\n", + " 'tool_call_id': {'title': 'Tool Call Id', 'type': 'string'}},\n", + " 'required': ['content', 'tool_call_id']},\n", " 'ChatPromptValueConcrete': {'title': 'ChatPromptValueConcrete',\n", " 'description': 'Chat prompt value which explicitly lists out the message types it accepts.\\nFor use in external schemas.',\n", " 'type': 'object',\n", @@ -223,7 +254,8 @@ " {'$ref': '#/definitions/HumanMessage'},\n", " {'$ref': '#/definitions/ChatMessage'},\n", " {'$ref': '#/definitions/SystemMessage'},\n", - " {'$ref': '#/definitions/FunctionMessage'}]}},\n", + " {'$ref': '#/definitions/FunctionMessage'},\n", + " {'$ref': '#/definitions/ToolMessage'}]}},\n", " 'type': {'title': 'Type',\n", " 'default': 'ChatPromptValueConcrete',\n", " 'enum': ['ChatPromptValueConcrete'],\n", @@ -231,7 +263,7 @@ " 'required': ['messages']}}}" ] }, - "execution_count": 15, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -254,7 +286,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 5, "id": "a0e41fd3-77d8-4911-af6a-d4d3aad5f77b", "metadata": {}, "outputs": [ @@ -262,37 +294,47 @@ "data": { "text/plain": [ "{'title': 'ChatOpenAIOutput',\n", - " 'anyOf': [{'$ref': '#/definitions/HumanMessage'},\n", - " {'$ref': '#/definitions/AIMessage'},\n", + " 'anyOf': [{'$ref': '#/definitions/AIMessage'},\n", + " {'$ref': '#/definitions/HumanMessage'},\n", " {'$ref': '#/definitions/ChatMessage'},\n", + " {'$ref': '#/definitions/SystemMessage'},\n", " {'$ref': '#/definitions/FunctionMessage'},\n", - " {'$ref': '#/definitions/SystemMessage'}],\n", - " 'definitions': {'HumanMessage': {'title': 'HumanMessage',\n", - " 'description': 'A Message from a human.',\n", + " {'$ref': '#/definitions/ToolMessage'}],\n", + " 'definitions': {'AIMessage': {'title': 'AIMessage',\n", + " 'description': 'A Message from an AI.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", - " 'default': 'human',\n", - " 'enum': ['human'],\n", + " 'default': 'ai',\n", + " 'enum': ['ai'],\n", " 'type': 'string'},\n", " 'example': {'title': 'Example', 'default': False, 'type': 'boolean'}},\n", " 'required': ['content']},\n", - " 'AIMessage': {'title': 'AIMessage',\n", - " 'description': 'A Message from an AI.',\n", + " 'HumanMessage': {'title': 'HumanMessage',\n", + " 'description': 'A Message from a human.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", - " 'default': 'ai',\n", - " 'enum': ['ai'],\n", + " 'default': 'human',\n", + " 'enum': ['human'],\n", " 'type': 'string'},\n", " 'example': {'title': 'Example', 'default': False, 'type': 'boolean'}},\n", " 'required': ['content']},\n", " 'ChatMessage': {'title': 'ChatMessage',\n", " 'description': 'A Message that can be assigned an arbitrary speaker (i.e. role).',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'chat',\n", @@ -300,10 +342,26 @@ " 'type': 'string'},\n", " 'role': {'title': 'Role', 'type': 'string'}},\n", " 'required': ['content', 'role']},\n", + " 'SystemMessage': {'title': 'SystemMessage',\n", + " 'description': 'A Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.',\n", + " 'type': 'object',\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", + " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", + " 'type': {'title': 'Type',\n", + " 'default': 'system',\n", + " 'enum': ['system'],\n", + " 'type': 'string'}},\n", + " 'required': ['content']},\n", " 'FunctionMessage': {'title': 'FunctionMessage',\n", " 'description': 'A Message for passing the result of executing a function back to a model.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", " 'default': 'function',\n", @@ -311,19 +369,23 @@ " 'type': 'string'},\n", " 'name': {'title': 'Name', 'type': 'string'}},\n", " 'required': ['content', 'name']},\n", - " 'SystemMessage': {'title': 'SystemMessage',\n", - " 'description': 'A Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.',\n", + " 'ToolMessage': {'title': 'ToolMessage',\n", + " 'description': 'A Message for passing the result of executing a tool back to a model.',\n", " 'type': 'object',\n", - " 'properties': {'content': {'title': 'Content', 'type': 'string'},\n", + " 'properties': {'content': {'title': 'Content',\n", + " 'anyOf': [{'type': 'string'},\n", + " {'type': 'array',\n", + " 'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n", " 'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n", " 'type': {'title': 'Type',\n", - " 'default': 'system',\n", - " 'enum': ['system'],\n", - " 'type': 'string'}},\n", - " 'required': ['content']}}}" + " 'default': 'tool',\n", + " 'enum': ['tool'],\n", + " 'type': 'string'},\n", + " 'tool_call_id': {'title': 'Tool Call Id', 'type': 'string'}},\n", + " 'required': ['content', 'tool_call_id']}}}" ] }, - "execution_count": 17, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -343,7 +405,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 6, "id": "bea9639d", "metadata": {}, "outputs": [ @@ -351,6 +413,8 @@ "name": "stdout", "output_type": "stream", "text": [ + "Sure, here's a bear-themed joke for you:\n", + "\n", "Why don't bears wear shoes?\n", "\n", "Because they already have bear feet!" @@ -372,17 +436,17 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 7, "id": "470e483f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they already have bear feet!\")" + "AIMessage(content=\"Why don't bears wear shoes? \\n\\nBecause they have bear feet!\")" ] }, - "execution_count": 21, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -401,18 +465,18 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 8, "id": "9685de67", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\"),\n", + "[AIMessage(content=\"Sure, here's a bear joke for you:\\n\\nWhy don't bears wear shoes?\\n\\nBecause they already have bear feet!\"),\n", " AIMessage(content=\"Why don't cats play poker in the wild?\\n\\nToo many cheetahs!\")]" ] }, - "execution_count": 22, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -431,18 +495,18 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 9, "id": "a08522f6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[AIMessage(content=\"Why don't bears wear shoes? \\n\\nBecause they have bear feet!\"),\n", - " AIMessage(content=\"Why don't cats play poker in the wild?\\n\\nToo many cheetahs!\")]" + "[AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\"),\n", + " AIMessage(content=\"Why don't cats play poker in the wild? Too many cheetahs!\")]" ] }, - "execution_count": 23, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -461,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 10, "id": "ea35eee4", "metadata": {}, "outputs": [ @@ -469,11 +533,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sure, here's a bear-themed joke for you:\n", - "\n", "Why don't bears wear shoes?\n", "\n", - "Because they already have bear feet!" + "Because they have bear feet!" ] } ], @@ -492,17 +554,17 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 11, "id": "ef8c9b20", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content=\"Why don't bears wear shoes? \\n\\nBecause they have bear feet!\")" + "AIMessage(content=\"Why don't bears ever wear shoes?\\n\\nBecause they already have bear feet!\")" ] }, - "execution_count": 25, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -521,7 +583,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 12, "id": "eba2a103", "metadata": {}, "outputs": [ @@ -531,7 +593,7 @@ "[AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\")]" ] }, - "execution_count": 26, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -540,6 +602,192 @@ "await chain.abatch([{\"topic\": \"bears\"}])" ] }, + { + "cell_type": "markdown", + "id": "c2d58e3f-2b2e-4dac-820b-5e9c263b1868", + "metadata": {}, + "source": [ + "## Async Stream Events (beta)" + ] + }, + { + "cell_type": "markdown", + "id": "53d365e5-dc14-4bb7-aa6a-7762c3af16a4", + "metadata": {}, + "source": [ + "Event Streaming is a **beta** API, and may change a bit based on feedback.\n", + "\n", + "Note: Introduced in langchain-core 0.2.0\n", + "\n", + "For now, when using the astream_events API, for everything to work properly please:\n", + "\n", + "* Use `async` throughout the code (including async tools etc)\n", + "* Propagate callbacks if defining custom functions / runnables. \n", + "* Whenever using runnables without LCEL, make sure to call `.astream()` on LLMs rather than `.ainvoke` to force the LLM to stream tokens.\n", + "\n", + "### Event Reference\n", + "\n", + "\n", + "Here is a reference table that shows some events that might be emitted by the various Runnable objects.\n", + "Definitions for some of the Runnable are included after the table.\n", + "\n", + "⚠️ When streaming the inputs for the runnable will not be available until the input stream has been entirely consumed This means that the inputs will be available at for the corresponding `end` hook rather than `start` event.\n", + "\n", + "\n", + "| event | name | chunk | input | output |\n", + "|----------------------|------------------|---------------------------------|-----------------------------------------------|-------------------------------------------------|\n", + "| on_chat_model_start | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | |\n", + "| on_chat_model_stream | [model name] | AIMessageChunk(content=\"hello\") | | |\n", + "| on_chat_model_end | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | {\"generations\": [...], \"llm_output\": None, ...} |\n", + "| on_llm_start | [model name] | | {'input': 'hello'} | |\n", + "| on_llm_stream | [model name] | 'Hello' | | |\n", + "| on_llm_end | [model name] | | 'Hello human!' |\n", + "| on_chain_start | format_docs | | | |\n", + "| on_chain_stream | format_docs | \"hello world!, goodbye world!\" | | |\n", + "| on_chain_end | format_docs | | [Document(...)] | \"hello world!, goodbye world!\" |\n", + "| on_tool_start | some_tool | | {\"x\": 1, \"y\": \"2\"} | |\n", + "| on_tool_stream | some_tool | {\"x\": 1, \"y\": \"2\"} | | |\n", + "| on_tool_end | some_tool | | | {\"x\": 1, \"y\": \"2\"} |\n", + "| on_retriever_start | [retriever name] | | {\"query\": \"hello\"} | |\n", + "| on_retriever_chunk | [retriever name] | {documents: [...]} | | |\n", + "| on_retriever_end | [retriever name] | | {\"query\": \"hello\"} | {documents: [...]} |\n", + "| on_prompt_start | [template_name] | | {\"question\": \"hello\"} | |\n", + "| on_prompt_end | [template_name] | | {\"question\": \"hello\"} | ChatPromptValue(messages: [SystemMessage, ...]) |\n", + "\n", + "\n", + "Here are declarations associated with the events shown above:\n", + "\n", + "`format_docs`:\n", + "\n", + "```python\n", + "def format_docs(docs: List[Document]) -> str:\n", + " '''Format the docs.'''\n", + " return \", \".join([doc.page_content for doc in docs])\n", + "\n", + "format_docs = RunnableLambda(format_docs)\n", + "```\n", + "\n", + "`some_tool`:\n", + "\n", + "```python\n", + "@tool\n", + "def some_tool(x: int, y: str) -> dict:\n", + " '''Some_tool.'''\n", + " return {\"x\": x, \"y\": y}\n", + "```\n", + "\n", + "`prompt`:\n", + "\n", + "```python\n", + "template = ChatPromptTemplate.from_messages(\n", + " [(\"system\", \"You are Cat Agent 007\"), (\"human\", \"{question}\")]\n", + ").with_config({\"run_name\": \"my_template\", \"tags\": [\"my_template\"]})\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "108cf792-a372-4626-bbef-9d7be23dde33", + "metadata": {}, + "source": [ + "Let's define a new chain to make it more interesting to show off the `astream_events` interface (and later the `astream_log` interface)." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "92eeb4da-0aae-457b-bd8f-8c35a024d4d1", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "\n", + "vectorstore = FAISS.from_texts(\n", + " [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n", + ")\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "retrieval_chain = (\n", + " {\n", + " \"context\": retriever.with_config(run_name=\"Docs\"),\n", + " \"question\": RunnablePassthrough(),\n", + " }\n", + " | prompt\n", + " | model.with_config(run_name=\"my_llm\")\n", + " | StrOutputParser()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1167e8f2-cab7-45b4-8922-7518b58a7d8d", + "metadata": {}, + "source": [ + "Now let's use `astream_events` to get events from the retriever and the LLM." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "0742d723-5b00-4a44-961e-dd4a3ec6d557", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/eugene/src/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: This API is in beta and may change in the future.\n", + " warn_beta(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--\n", + "Retrieved the following documents:\n", + "[Document(page_content='harrison worked at kensho')]\n", + "\n", + "Streaming LLM:\n", + "|H|arrison| worked| at| Kens|ho|.||\n", + "Done streaming LLM.\n" + ] + } + ], + "source": [ + "async for event in retrieval_chain.astream_events(\n", + " \"where did harrison work?\", version=\"v1\", include_names=[\"Docs\", \"my_llm\"]\n", + "):\n", + " kind = event[\"event\"]\n", + " if kind == \"on_chat_model_stream\":\n", + " print(event[\"data\"][\"chunk\"].content, end=\"|\")\n", + " elif kind in {\"on_chat_model_start\"}:\n", + " print()\n", + " print(\"Streaming LLM:\")\n", + " elif kind in {\"on_chat_model_end\"}:\n", + " print()\n", + " print(\"Done streaming LLM.\")\n", + " elif kind == \"on_retriever_end\":\n", + " print(\"--\")\n", + " print(\"Retrieved the following documents:\")\n", + " print(event[\"data\"][\"output\"][\"documents\"])\n", + " elif kind == \"on_tool_end\":\n", + " print(f\"Ended tool: {event['name']}\")\n", + " else:\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "f9cef104", @@ -607,7 +855,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 15, "id": "21c9019e", "metadata": {}, "outputs": [ @@ -619,20 +867,23 @@ "RunLogPatch({'op': 'replace',\n", " 'path': '',\n", " 'value': {'final_output': None,\n", - " 'id': 'e2f2cc72-eb63-4d20-8326-237367482efb',\n", + " 'id': '82e9b4b1-3dd6-4732-8db9-90e79c4da48c',\n", " 'logs': {},\n", - " 'streamed_output': []}})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': [],\n", + " 'type': 'chain'}})\n", "----------------------------------------\n", "RunLogPatch({'op': 'add',\n", " 'path': '/logs/Docs',\n", " 'value': {'end_time': None,\n", " 'final_output': None,\n", - " 'id': '8da492cc-4492-4e74-b8b0-9e60e8693390',\n", + " 'id': '9206e94a-57bd-48ee-8c5e-fdd1c52a6da2',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:50:13.526',\n", + " 'start_time': '2024-01-19T22:33:55.902+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}})\n", "----------------------------------------\n", "RunLogPatch({'op': 'add',\n", @@ -640,60 +891,41 @@ " 'value': {'documents': [Document(page_content='harrison worked at kensho')]}},\n", " {'op': 'add',\n", " 'path': '/logs/Docs/end_time',\n", - " 'value': '2023-10-19T17:50:13.713'})\n", - "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ''})\n", + " 'value': '2024-01-19T22:33:56.064+00:00'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': 'H'})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ''},\n", + " {'op': 'replace', 'path': '/final_output', 'value': ''})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': 'arrison'})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': 'H'},\n", + " {'op': 'replace', 'path': '/final_output', 'value': 'H'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ' worked'})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': 'arrison'},\n", + " {'op': 'replace', 'path': '/final_output', 'value': 'Harrison'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ' at'})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ' worked'},\n", + " {'op': 'replace', 'path': '/final_output', 'value': 'Harrison worked'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ' Kens'})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ' at'},\n", + " {'op': 'replace', 'path': '/final_output', 'value': 'Harrison worked at'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': 'ho'})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ' Kens'},\n", + " {'op': 'replace', 'path': '/final_output', 'value': 'Harrison worked at Kens'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': '.'})\n", - "----------------------------------------\n", - "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ''})\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': 'ho'},\n", + " {'op': 'replace',\n", + " 'path': '/final_output',\n", + " 'value': 'Harrison worked at Kensho'})\n", "----------------------------------------\n", - "RunLogPatch({'op': 'replace',\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': '.'},\n", + " {'op': 'replace',\n", " 'path': '/final_output',\n", - " 'value': {'output': 'Harrison worked at Kensho.'}})\n" + " 'value': 'Harrison worked at Kensho.'})\n", + "----------------------------------------\n", + "RunLogPatch({'op': 'add', 'path': '/streamed_output/-', 'value': ''})\n" ] } ], "source": [ - "from langchain_community.vectorstores import FAISS\n", - "from langchain_core.output_parsers import StrOutputParser\n", - "from langchain_core.runnables import RunnablePassthrough\n", - "from langchain_openai import OpenAIEmbeddings\n", - "\n", - "template = \"\"\"Answer the question based only on the following context:\n", - "{context}\n", - "\n", - "Question: {question}\n", - "\"\"\"\n", - "prompt = ChatPromptTemplate.from_template(template)\n", - "\n", - "vectorstore = FAISS.from_texts(\n", - " [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n", - ")\n", - "retriever = vectorstore.as_retriever()\n", - "\n", - "retrieval_chain = (\n", - " {\n", - " \"context\": retriever.with_config(run_name=\"Docs\"),\n", - " \"question\": RunnablePassthrough(),\n", - " }\n", - " | prompt\n", - " | model\n", - " | StrOutputParser()\n", - ")\n", - "\n", "async for chunk in retrieval_chain.astream_log(\n", " \"where did harrison work?\", include_names=[\"Docs\"]\n", "):\n", @@ -714,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 16, "id": "5c26b731-b4eb-4967-a42a-dec813249ecb", "metadata": {}, "outputs": [ @@ -724,172 +956,185 @@ "text": [ "----------------------------------------------------------------------\n", "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", " 'logs': {},\n", - " 'streamed_output': []})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': [],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", " 'logs': {'Docs': {'end_time': None,\n", " 'final_output': None,\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", - " 'metadata': {},\n", - " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", - " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", - " 'type': 'retriever'}},\n", - " 'streamed_output': []})\n", - "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", - " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': []})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': [],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': [],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': '',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': [''],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'H',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H', 'arrison']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H', 'arrison', ' worked']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H', 'arrison'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison worked',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H', 'arrison', ' worked'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison worked at',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at', ' Kens']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison worked at Kens',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at', ' Kens', 'ho']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at', ' Kens'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison worked at Kensho',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at', ' Kens', 'ho', '.']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at', ' Kens', 'ho'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': None,\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison worked at Kensho.',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", - " 'streamed_output': ['',\n", - " 'H',\n", - " 'arrison',\n", - " ' worked',\n", - " ' at',\n", - " ' Kens',\n", - " 'ho',\n", - " '.',\n", - " '']})\n", + " 'name': 'RunnableSequence',\n", + " 'streamed_output': ['', 'H', 'arrison', ' worked', ' at', ' Kens', 'ho', '.'],\n", + " 'type': 'chain'})\n", "----------------------------------------------------------------------\n", - "RunLog({'final_output': {'output': 'Harrison worked at Kensho.'},\n", - " 'id': 'afe66178-d75f-4c2d-b348-b1d144239cd6',\n", - " 'logs': {'Docs': {'end_time': '2023-10-19T17:52:15.738',\n", + "RunLog({'final_output': 'Harrison worked at Kensho.',\n", + " 'id': '431d1c55-7c50-48ac-b3a2-2f5ba5f35172',\n", + " 'logs': {'Docs': {'end_time': '2024-01-19T22:33:57.120+00:00',\n", " 'final_output': {'documents': [Document(page_content='harrison worked at kensho')]},\n", - " 'id': '88d51118-5756-4891-89c5-2f6a5e90cc26',\n", + " 'id': '8de10b49-d6af-4cb7-a4e7-fbadf6efa01e',\n", " 'metadata': {},\n", " 'name': 'Docs',\n", - " 'start_time': '2023-10-19T17:52:15.438',\n", + " 'start_time': '2024-01-19T22:33:56.939+00:00',\n", + " 'streamed_output': [],\n", " 'streamed_output_str': [],\n", - " 'tags': ['map:key:context', 'FAISS'],\n", + " 'tags': ['map:key:context', 'FAISS', 'OpenAIEmbeddings'],\n", " 'type': 'retriever'}},\n", + " 'name': 'RunnableSequence',\n", " 'streamed_output': ['',\n", " 'H',\n", " 'arrison',\n", @@ -898,7 +1143,8 @@ " ' Kens',\n", " 'ho',\n", " '.',\n", - " '']})\n" + " ''],\n", + " 'type': 'chain'})\n" ] } ], @@ -923,7 +1169,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 17, "id": "0a1c409d", "metadata": {}, "outputs": [], @@ -940,7 +1186,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 18, "id": "08044c0a", "metadata": {}, "outputs": [ @@ -948,8 +1194,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 54.3 ms, sys: 0 ns, total: 54.3 ms\n", - "Wall time: 2.29 s\n" + "CPU times: user 18 ms, sys: 1.27 ms, total: 19.3 ms\n", + "Wall time: 692 ms\n" ] }, { @@ -958,7 +1204,7 @@ "AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they already have bear feet!\")" ] }, - "execution_count": 43, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -970,7 +1216,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 19, "id": "22c56804", "metadata": {}, "outputs": [ @@ -978,17 +1224,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 7.8 ms, sys: 0 ns, total: 7.8 ms\n", - "Wall time: 1.43 s\n" + "CPU times: user 10.5 ms, sys: 166 µs, total: 10.7 ms\n", + "Wall time: 579 ms\n" ] }, { "data": { "text/plain": [ - "AIMessage(content=\"In wild embrace,\\nNature's strength roams with grace.\")" + "AIMessage(content=\"In forest's embrace,\\nMajestic bears pace.\")" ] }, - "execution_count": 44, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1000,7 +1246,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 20, "id": "4fff4cbb", "metadata": {}, "outputs": [ @@ -1008,18 +1254,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 167 ms, sys: 921 µs, total: 168 ms\n", - "Wall time: 1.56 s\n" + "CPU times: user 32 ms, sys: 2.59 ms, total: 34.6 ms\n", + "Wall time: 816 ms\n" ] }, { "data": { "text/plain": [ - "{'joke': AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they already have bear feet!\"),\n", - " 'poem': AIMessage(content=\"Fierce and wild, nature's might,\\nBears roam the woods, shadows of the night.\")}" + "{'joke': AIMessage(content=\"Sure, here's a bear-related joke for you:\\n\\nWhy did the bear bring a ladder to the bar?\\n\\nBecause he heard the drinks were on the house!\"),\n", + " 'poem': AIMessage(content=\"In wilderness they roam,\\nMajestic strength, nature's throne.\")}" ] }, - "execution_count": 45, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1042,7 +1288,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 21, "id": "f67d2268-c766-441b-8d64-57b8219ccb34", "metadata": {}, "outputs": [ @@ -1050,18 +1296,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 159 ms, sys: 3.66 ms, total: 163 ms\n", - "Wall time: 1.34 s\n" + "CPU times: user 17.3 ms, sys: 4.84 ms, total: 22.2 ms\n", + "Wall time: 628 ms\n" ] }, { "data": { "text/plain": [ - "[AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they already have bear feet!\"),\n", - " AIMessage(content=\"Sure, here's a cat joke for you:\\n\\nWhy don't cats play poker in the wild?\\n\\nBecause there are too many cheetahs!\")]" + "[AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\"),\n", + " AIMessage(content=\"Why don't cats play poker in the wild?\\n\\nToo many cheetahs!\")]" ] }, - "execution_count": 40, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -1073,7 +1319,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 22, "id": "83c8d511-9563-403e-9c06-cae986cf5dee", "metadata": {}, "outputs": [ @@ -1081,18 +1327,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 165 ms, sys: 0 ns, total: 165 ms\n", - "Wall time: 1.73 s\n" + "CPU times: user 15.8 ms, sys: 3.83 ms, total: 19.7 ms\n", + "Wall time: 718 ms\n" ] }, { "data": { "text/plain": [ - "[AIMessage(content=\"Silent giants roam,\\nNature's strength, love's emblem shown.\"),\n", - " AIMessage(content='Whiskers aglow, paws tiptoe,\\nGraceful hunters, hearts aglow.')]" + "[AIMessage(content='In the wild, bears roam,\\nMajestic guardians of ancient home.'),\n", + " AIMessage(content='Whiskers grace, eyes gleam,\\nCats dance through the moonbeam.')]" ] }, - "execution_count": 41, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1104,7 +1350,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 23, "id": "07a81230-8db8-4b96-bdcb-99ae1d171f2f", "metadata": {}, "outputs": [ @@ -1112,20 +1358,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 507 ms, sys: 125 ms, total: 632 ms\n", - "Wall time: 1.49 s\n" + "CPU times: user 44.8 ms, sys: 3.17 ms, total: 48 ms\n", + "Wall time: 721 ms\n" ] }, { "data": { "text/plain": [ - "[{'joke': AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they already have bear feet!\"),\n", - " 'poem': AIMessage(content=\"Majestic bears roam,\\nNature's wild guardians of home.\")},\n", - " {'joke': AIMessage(content=\"Sure, here's a cat joke for you:\\n\\nWhy did the cat sit on the computer?\\n\\nBecause it wanted to keep an eye on the mouse!\"),\n", - " 'poem': AIMessage(content='Whiskers twitch, eyes gleam,\\nGraceful creatures, feline dream.')}]" + "[{'joke': AIMessage(content=\"Sure, here's a bear joke for you:\\n\\nWhy don't bears wear shoes?\\n\\nBecause they have bear feet!\"),\n", + " 'poem': AIMessage(content=\"Majestic bears roam,\\nNature's strength, beauty shown.\")},\n", + " {'joke': AIMessage(content=\"Why don't cats play poker in the wild?\\n\\nToo many cheetahs!\"),\n", + " 'poem': AIMessage(content=\"Whiskers dance, eyes aglow,\\nCats embrace the night's gentle flow.\")}]" ] }, - "execution_count": 42, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -1134,14 +1380,6 @@ "%%time\n", "combined.batch([{\"topic\": \"bears\"}, {\"topic\": \"cats\"}])" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5218cafd-370a-4e3a-85a0-452e570092fe", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -1160,7 +1398,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.11.4" } }, "nbformat": 4, From 1f4ac62deead73b66b0e48cfdb06988895dc8dc8 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Tue, 23 Jan 2024 12:08:17 -0700 Subject: [PATCH 183/215] cli[patch], google-vertexai[patch]: readme template (#16470) --- .../integration_template/README.md | 44 +++++++++++++++++++ libs/partners/google-vertexai/README.md | 4 +- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/libs/cli/langchain_cli/integration_template/README.md b/libs/cli/langchain_cli/integration_template/README.md index e1f3e352472a9..19c73c4a5ba12 100644 --- a/libs/cli/langchain_cli/integration_template/README.md +++ b/libs/cli/langchain_cli/integration_template/README.md @@ -1 +1,45 @@ # __package_name__ + +This package contains the LangChain integration with __ModuleName__ + +## Installation + +```bash +pip install -U __package_name__ +``` + +And you should configure credentials by setting the following environment variables: + +* TODO: fill this out + +## Chat Models + +`Chat__ModuleName__` class exposes chat models from __ModuleName__. + +```python +from __module_name__ import Chat__ModuleName__ + +llm = Chat__ModuleName__() +llm.invoke("Sing a ballad of LangChain.") +``` + +## Embeddings + +`__ModuleName__Embeddings` class exposes embeddings from __ModuleName__. + +```python +from __module_name__ import __ModuleName__Embeddings + +embeddings = __ModuleName__Embeddings() +embeddings.embed_query("What is the meaning of life?") +``` + +## LLMs +`__ModuleName__LLM` class exposes LLMs from __ModuleName__. + +```python +from __module_name__ import __ModuleName__LLM + +llm = __ModuleName__LLM() +llm.invoke("The meaning of life is") +``` diff --git a/libs/partners/google-vertexai/README.md b/libs/partners/google-vertexai/README.md index 6a4839254fc2d..0637bd7cc9119 100644 --- a/libs/partners/google-vertexai/README.md +++ b/libs/partners/google-vertexai/README.md @@ -10,7 +10,7 @@ pip install -U langchain-google-vertexai ## Chat Models -`ChatVertexAI` class exposes models . +`ChatVertexAI` class exposes models such as `gemini-pro` and `chat-bison`. To use, you should have Google Cloud project with APIs enabled, and configured credentials. Initialize the model as: @@ -63,7 +63,7 @@ The value of `image_url` can be any of the following: You can use Google Cloud's embeddings models as: -``` +```python from langchain_google_vertexai import VertexAIEmbeddings embeddings = VertexAIEmbeddings() From ef6a33557079c8c691fb18c78d7c89c2d2e63888 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:31:50 -0800 Subject: [PATCH 184/215] core[patch]: Release 0.1.15 (#16473) --- libs/core/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 2a5ddba98ae94..7c12a8ea300f4 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-core" -version = "0.1.14" +version = "0.1.15" description = "Building applications with LLMs through composability" authors = [] license = "MIT" From 54149292f8e95ea83882a32937ddb32a17ef0465 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:50:10 -0800 Subject: [PATCH 185/215] community[patch]: Release 0.0.15 (#16474) --- libs/community/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/pyproject.toml b/libs/community/pyproject.toml index 465afacece680..9a781bc75b05c 100644 --- a/libs/community/pyproject.toml +++ b/libs/community/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-community" -version = "0.0.14" +version = "0.0.15" description = "Community contributed LangChain integrations." authors = [] license = "MIT" From ba326b98d02a40307cef44dfd4e7bd27ef62885e Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:50:25 -0800 Subject: [PATCH 186/215] langchain[patch]: Release 0.1.3 (#16475) --- libs/langchain/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index 7d370cfa05759..ebd84e5f36501 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain" -version = "0.1.2" +version = "0.1.3" description = "Building applications with LLMs through composability" authors = [] license = "MIT" @@ -14,6 +14,7 @@ langchain-server = "langchain.server:main" python = ">=3.8.1,<4.0" langchain-core = ">=0.1.14,<0.2" langchain-community = ">=0.0.14,<0.1" +langsmith = ">=0.0.83,<0.1" pydantic = ">=1,<3" SQLAlchemy = ">=1.4,<3" requests = "^2" @@ -80,7 +81,6 @@ cassio = {version = "^0.1.0", optional = true} sympy = {version = "^1.12", optional = true} rapidfuzz = {version = "^3.1.1", optional = true} jsonschema = {version = ">1", optional = true} -langsmith = ">=0.0.83,<0.1" rank-bm25 = {version = "^0.2.2", optional = true} geopandas = {version = "^0.13.1", optional = true} gitpython = {version = "^3.1.32", optional = true} From c3530f1c110f144ee8919ec3b37dcdd473c97ed4 Mon Sep 17 00:00:00 2001 From: Lance Martin <122662504+rlancemartin@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:23:08 -0800 Subject: [PATCH 187/215] templates: Minor nit on HyDE (#16478) --- templates/hyde/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/hyde/README.md b/templates/hyde/README.md index 8edfecb7f413c..35ea291eca18b 100644 --- a/templates/hyde/README.md +++ b/templates/hyde/README.md @@ -1,7 +1,7 @@ # hyde -This template HyDE with RAG. +This template uses HyDE with RAG. Hyde is a retrieval method that stands for Hypothetical Document Embeddings (HyDE). It is a method used to enhance retrieval by generating a hypothetical document for an incoming query. From 51c8ef6af43cf4394bb276c0de39dbcd908c8a10 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Tue, 23 Jan 2024 14:58:06 -0700 Subject: [PATCH 188/215] templates: fix azure params in retrieval agent (#16257) - FIX templates/retrieval-agent/retireval-agent/chain.py to use the new Syntax for Azure env params - cr --------- Co-authored-by: braun-viathan <p.braun@viathan.de> Co-authored-by: Braun-viathan <121631422+braun-viathan@users.noreply.github.com> --- templates/retrieval-agent/README.md | 3 +- templates/retrieval-agent/poetry.lock | 337 +++++++++++++----- templates/retrieval-agent/pyproject.toml | 3 +- .../retrieval-agent/retrieval_agent/chain.py | 9 +- 4 files changed, 261 insertions(+), 91 deletions(-) diff --git a/templates/retrieval-agent/README.md b/templates/retrieval-agent/README.md index 1e4fae3b9e006..7b3a32bd909c4 100644 --- a/templates/retrieval-agent/README.md +++ b/templates/retrieval-agent/README.md @@ -8,10 +8,9 @@ By default, this does retrieval over Arxiv. Since we are using Azure OpenAI, we will need to set the following environment variables: ```shell -export AZURE_OPENAI_API_BASE=... +export AZURE_OPENAI_ENDPOINT=... export AZURE_OPENAI_API_VERSION=... export AZURE_OPENAI_API_KEY=... -export AZURE_OPENAI_DEPLOYMENT_NAME=... ``` ## Usage diff --git a/templates/retrieval-agent/poetry.lock b/templates/retrieval-agent/poetry.lock index a482a7bd0c832..28eddbde4f15a 100644 --- a/templates/retrieval-agent/poetry.lock +++ b/templates/retrieval-agent/poetry.lock @@ -501,20 +501,20 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.40" +version = "3.1.41" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, - {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, + {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, + {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] [[package]] name = "greenlet" @@ -692,13 +692,13 @@ files = [ [[package]] name = "langchain" -version = "0.1.0" +version = "0.1.1" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain-0.1.0-py3-none-any.whl", hash = "sha256:8652e74b039333a55c79faff4400b077ba1bd0ddce5255574e42d301c05c1733"}, - {file = "langchain-0.1.0.tar.gz", hash = "sha256:d43119f8d3fda2c8ddf8c3a19bd5b94b347e27d1867ff14a921b90bdbed0668a"}, + {file = "langchain-0.1.1-py3-none-any.whl", hash = "sha256:3f1dcf458bbd603447e93ece99fe6611b1fafa16dc67464b1c8091dd475242f9"}, + {file = "langchain-0.1.1.tar.gz", hash = "sha256:a9616544b78ccf1a5b286fae7926e00beea6dc5b8fda983e5180313fefd3dfab"}, ] [package.dependencies] @@ -706,8 +706,8 @@ aiohttp = ">=3.8.3,<4.0.0" async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} dataclasses-json = ">=0.5.7,<0.7" jsonpatch = ">=1.33,<2.0" -langchain-community = ">=0.0.9,<0.1" -langchain-core = ">=0.1.7,<0.2" +langchain-community = ">=0.0.13,<0.1" +langchain-core = ">=0.1.9,<0.2" langsmith = ">=0.0.77,<0.1.0" numpy = ">=1,<2" pydantic = ">=1,<3" @@ -750,19 +750,19 @@ uvicorn = ">=0.23.2,<0.24.0" [[package]] name = "langchain-community" -version = "0.0.9" +version = "0.0.13" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_community-0.0.9-py3-none-any.whl", hash = "sha256:21e1f96c776541255b7067f32aafbf065f78a33be8f0e2660080ddc3e9ed48b7"}, - {file = "langchain_community-0.0.9.tar.gz", hash = "sha256:b14f10b249fd61b0b8e3d2896f85c2d577eb4a5e2ae01291e2a4ebbe1bb3c370"}, + {file = "langchain_community-0.0.13-py3-none-any.whl", hash = "sha256:655196e446e7f37f4882221b6f3f791d6add28ea596d521ccf6f4507386b9a13"}, + {file = "langchain_community-0.0.13.tar.gz", hash = "sha256:cf66c6ff7fcbeb582f5e88ee00decea7fdeca5ccddda725046f28efc697c41a7"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" -langchain-core = ">=0.1.7,<0.2" +langchain-core = ">=0.1.9,<0.2" langsmith = ">=0.0.63,<0.1.0" numpy = ">=1,<2" PyYAML = ">=5.3" @@ -776,13 +776,13 @@ extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15. [[package]] name = "langchain-core" -version = "0.1.7" +version = "0.1.12" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.7-py3-none-any.whl", hash = "sha256:c66327dbb4b7d4ab911556aa0511ebf4f40801ad66d98778fb5566dba45b0091"}, - {file = "langchain_core-0.1.7.tar.gz", hash = "sha256:c05211a309721d67aa5a681c946a2f010e14632a2bea3728da0a30a2534efa9e"}, + {file = "langchain_core-0.1.12-py3-none-any.whl", hash = "sha256:d11c6262f7a9deff7de8fdf14498b8a951020dfed3a80f2358ab731ad04abef0"}, + {file = "langchain_core-0.1.12.tar.gz", hash = "sha256:f18e9300e9a07589b3e280e51befbc5a4513f535949406e55eb7a2dc40c3ce66"}, ] [package.dependencies] @@ -798,15 +798,32 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] extended-testing = ["jinja2 (>=3,<4)"] +[[package]] +name = "langchain-openai" +version = "0.0.2.post1" +description = "An integration package connecting OpenAI and LangChain" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_openai-0.0.2.post1-py3-none-any.whl", hash = "sha256:ba468b94c23da9d8ccefe5d5a3c1c65b4b9702292523e53acc689a9110022e26"}, + {file = "langchain_openai-0.0.2.post1.tar.gz", hash = "sha256:f8e78db4a663feeac71d9f036b9422406c199ea3ef4c97d99ff392c93530e073"}, +] + +[package.dependencies] +langchain-core = ">=0.1.7,<0.2" +numpy = ">=1,<2" +openai = ">=1.6.1,<2.0.0" +tiktoken = ">=0.5.2,<0.6.0" + [[package]] name = "langserve" -version = "0.0.38" +version = "0.0.39" description = "" optional = false python-versions = ">=3.8.1,<4.0.0" files = [ - {file = "langserve-0.0.38-py3-none-any.whl", hash = "sha256:c43019fb74dd50a95561cba5bdc9f6ac43e28ea08fba01a7f5910815fca60550"}, - {file = "langserve-0.0.38.tar.gz", hash = "sha256:384c56d0aabbc0af8f49edc1409a9ac0ba8fbab5d15e9f2312071dbdb053d47e"}, + {file = "langserve-0.0.39-py3-none-any.whl", hash = "sha256:3a8dc3af3c4a18b6867f1a32b75968035ea5ecf5481862fd9d4b51bb06f43e65"}, + {file = "langserve-0.0.39.tar.gz", hash = "sha256:c5ab2a539e7a008ca017cbe93dd6210aca9f382989906eda307464eac7182a41"}, ] [package.dependencies] @@ -825,13 +842,13 @@ server = ["fastapi (>=0.90.1,<1)", "sse-starlette (>=1.3.0,<2.0.0)"] [[package]] name = "langsmith" -version = "0.0.77" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.77-py3-none-any.whl", hash = "sha256:750c0aa9177240c64e131d831e009ed08dd59038f7cabbd0bbcf62ccb7c8dcac"}, - {file = "langsmith-0.0.77.tar.gz", hash = "sha256:c4c8d3a96ad8671a41064f3ccc673e2e22a4153e823b19f915c9c9b8a4f33a2c"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -864,22 +881,22 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "marshmallow" -version = "3.20.1" +version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, - {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, + {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, + {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -1026,13 +1043,13 @@ files = [ [[package]] name = "openai" -version = "1.6.1" +version = "1.8.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.6.1-py3-none-any.whl", hash = "sha256:bc9f774838d67ac29fb24cdeb2d58faf57de8b311085dcd1348f7aa02a96c7ee"}, - {file = "openai-1.6.1.tar.gz", hash = "sha256:d553ca9dbf9486b08e75b09e8671e4f638462aaadccfced632bf490fc3d75fa2"}, + {file = "openai-1.8.0-py3-none-any.whl", hash = "sha256:0f8f53805826103fdd8adaf379ad3ec23f9d867e698cbc14caf34b778d150175"}, + {file = "openai-1.8.0.tar.gz", hash = "sha256:93366be27802f517e89328801913d2a5ede45e3b86fdcab420385b8a1b88c767"}, ] [package.dependencies] @@ -1049,61 +1066,61 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] [[package]] name = "orjson" -version = "3.9.10" +version = "3.9.12" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c18a4da2f50050a03d1da5317388ef84a16013302a5281d6f64e4a3f406aabc4"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5148bab4d71f58948c7c39d12b14a9005b6ab35a0bdf317a8ade9a9e4d9d0bd5"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cf7837c3b11a2dfb589f8530b3cff2bd0307ace4c301e8997e95c7468c1378e"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c62b6fa2961a1dcc51ebe88771be5319a93fd89bd247c9ddf732bc250507bc2b"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb3922a7a804755bbe6b5be9b312e746137a03600f488290318936c1a2d4dc"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1234dc92d011d3554d929b6cf058ac4a24d188d97be5e04355f1b9223e98bbe9"}, - {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:06ad5543217e0e46fd7ab7ea45d506c76f878b87b1b4e369006bdb01acc05a83"}, - {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fd72fab7bddce46c6826994ce1e7de145ae1e9e106ebb8eb9ce1393ca01444d"}, - {file = "orjson-3.9.10-cp310-none-win32.whl", hash = "sha256:b5b7d4a44cc0e6ff98da5d56cde794385bdd212a86563ac321ca64d7f80c80d1"}, - {file = "orjson-3.9.10-cp310-none-win_amd64.whl", hash = "sha256:61804231099214e2f84998316f3238c4c2c4aaec302df12b21a64d72e2a135c7"}, - {file = "orjson-3.9.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cff7570d492bcf4b64cc862a6e2fb77edd5e5748ad715f487628f102815165e9"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8bc367f725dfc5cabeed1ae079d00369900231fbb5a5280cf0736c30e2adf7"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c812312847867b6335cfb264772f2a7e85b3b502d3a6b0586aa35e1858528ab1"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edd2856611e5050004f4722922b7b1cd6268da34102667bd49d2a2b18bafb81"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:674eb520f02422546c40401f4efaf8207b5e29e420c17051cddf6c02783ff5ca"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0dc4310da8b5f6415949bd5ef937e60aeb0eb6b16f95041b5e43e6200821fb"}, - {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99c625b8c95d7741fe057585176b1b8783d46ed4b8932cf98ee145c4facf499"}, - {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec6f18f96b47299c11203edfbdc34e1b69085070d9a3d1f302810cc23ad36bf3"}, - {file = "orjson-3.9.10-cp311-none-win32.whl", hash = "sha256:ce0a29c28dfb8eccd0f16219360530bc3cfdf6bf70ca384dacd36e6c650ef8e8"}, - {file = "orjson-3.9.10-cp311-none-win_amd64.whl", hash = "sha256:cf80b550092cc480a0cbd0750e8189247ff45457e5a023305f7ef1bcec811616"}, - {file = "orjson-3.9.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:602a8001bdf60e1a7d544be29c82560a7b49319a0b31d62586548835bbe2c862"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f295efcd47b6124b01255d1491f9e46f17ef40d3d7eabf7364099e463fb45f0f"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92af0d00091e744587221e79f68d617b432425a7e59328ca4c496f774a356071"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5a02360e73e7208a872bf65a7554c9f15df5fe063dc047f79738998b0506a14"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858379cbb08d84fe7583231077d9a36a1a20eb72f8c9076a45df8b083724ad1d"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666c6fdcaac1f13eb982b649e1c311c08d7097cbda24f32612dae43648d8db8d"}, - {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3fb205ab52a2e30354640780ce4587157a9563a68c9beaf52153e1cea9aa0921"}, - {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7ec960b1b942ee3c69323b8721df2a3ce28ff40e7ca47873ae35bfafeb4555ca"}, - {file = "orjson-3.9.10-cp312-none-win_amd64.whl", hash = "sha256:3e892621434392199efb54e69edfff9f699f6cc36dd9553c5bf796058b14b20d"}, - {file = "orjson-3.9.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8b9ba0ccd5a7f4219e67fbbe25e6b4a46ceef783c42af7dbc1da548eb28b6531"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e2ecd1d349e62e3960695214f40939bbfdcaeaaa62ccc638f8e651cf0970e5f"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f433be3b3f4c66016d5a20e5b4444ef833a1f802ced13a2d852c637f69729c1"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4689270c35d4bb3102e103ac43c3f0b76b169760aff8bcf2d401a3e0e58cdb7f"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd176f528a8151a6efc5359b853ba3cc0e82d4cd1fab9c1300c5d957dc8f48c"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a2ce5ea4f71681623f04e2b7dadede3c7435dfb5e5e2d1d0ec25b35530e277b"}, - {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:49f8ad582da6e8d2cf663c4ba5bf9f83cc052570a3a767487fec6af839b0e777"}, - {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a11b4b1a8415f105d989876a19b173f6cdc89ca13855ccc67c18efbd7cbd1f8"}, - {file = "orjson-3.9.10-cp38-none-win32.whl", hash = "sha256:a353bf1f565ed27ba71a419b2cd3db9d6151da426b61b289b6ba1422a702e643"}, - {file = "orjson-3.9.10-cp38-none-win_amd64.whl", hash = "sha256:e28a50b5be854e18d54f75ef1bb13e1abf4bc650ab9d635e4258c58e71eb6ad5"}, - {file = "orjson-3.9.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ee5926746232f627a3be1cc175b2cfad24d0170d520361f4ce3fa2fd83f09e1d"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a73160e823151f33cdc05fe2cea557c5ef12fdf276ce29bb4f1c571c8368a60"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c338ed69ad0b8f8f8920c13f529889fe0771abbb46550013e3c3d01e5174deef"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5869e8e130e99687d9e4be835116c4ebd83ca92e52e55810962446d841aba8de"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2c1e559d96a7f94a4f581e2a32d6d610df5840881a8cba8f25e446f4d792df3"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a3a3a72c9811b56adf8bcc829b010163bb2fc308877e50e9910c9357e78521"}, - {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f8fb7f5ecf4f6355683ac6881fd64b5bb2b8a60e3ccde6ff799e48791d8f864"}, - {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c943b35ecdf7123b2d81d225397efddf0bce2e81db2f3ae633ead38e85cd5ade"}, - {file = "orjson-3.9.10-cp39-none-win32.whl", hash = "sha256:fb0b361d73f6b8eeceba47cd37070b5e6c9de5beaeaa63a1cb35c7e1a73ef088"}, - {file = "orjson-3.9.10-cp39-none-win_amd64.whl", hash = "sha256:b90f340cb6397ec7a854157fac03f0c82b744abdd1c0941a024c3c29d1340aff"}, - {file = "orjson-3.9.10.tar.gz", hash = "sha256:9ebbdbd6a046c304b1845e96fbcc5559cd296b4dfd3ad2509e33c4d9ce07d6a1"}, + {file = "orjson-3.9.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6b4e2bed7d00753c438e83b613923afdd067564ff7ed696bfe3a7b073a236e07"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd1b8ec63f0bf54a50b498eedeccdca23bd7b658f81c524d18e410c203189365"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab8add018a53665042a5ae68200f1ad14c7953fa12110d12d41166f111724656"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12756a108875526b76e505afe6d6ba34960ac6b8c5ec2f35faf73ef161e97e07"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:890e7519c0c70296253660455f77e3a194554a3c45e42aa193cdebc76a02d82b"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d664880d7f016efbae97c725b243b33c2cbb4851ddc77f683fd1eec4a7894146"}, + {file = "orjson-3.9.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cfdaede0fa5b500314ec7b1249c7e30e871504a57004acd116be6acdda3b8ab3"}, + {file = "orjson-3.9.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6492ff5953011e1ba9ed1bf086835fd574bd0a3cbe252db8e15ed72a30479081"}, + {file = "orjson-3.9.12-cp310-none-win32.whl", hash = "sha256:29bf08e2eadb2c480fdc2e2daae58f2f013dff5d3b506edd1e02963b9ce9f8a9"}, + {file = "orjson-3.9.12-cp310-none-win_amd64.whl", hash = "sha256:0fc156fba60d6b50743337ba09f052d8afc8b64595112996d22f5fce01ab57da"}, + {file = "orjson-3.9.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2849f88a0a12b8d94579b67486cbd8f3a49e36a4cb3d3f0ab352c596078c730c"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3186b18754befa660b31c649a108a915493ea69b4fc33f624ed854ad3563ac65"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbbf313c9fb9d4f6cf9c22ced4b6682230457741daeb3d7060c5d06c2e73884a"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99e8cd005b3926c3db9b63d264bd05e1bf4451787cc79a048f27f5190a9a0311"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59feb148392d9155f3bfed0a2a3209268e000c2c3c834fb8fe1a6af9392efcbf"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4ae815a172a1f073b05b9e04273e3b23e608a0858c4e76f606d2d75fcabde0c"}, + {file = "orjson-3.9.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed398f9a9d5a1bf55b6e362ffc80ac846af2122d14a8243a1e6510a4eabcb71e"}, + {file = "orjson-3.9.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d3cfb76600c5a1e6be91326b8f3b83035a370e727854a96d801c1ea08b708073"}, + {file = "orjson-3.9.12-cp311-none-win32.whl", hash = "sha256:a2b6f5252c92bcab3b742ddb3ac195c0fa74bed4319acd74f5d54d79ef4715dc"}, + {file = "orjson-3.9.12-cp311-none-win_amd64.whl", hash = "sha256:c95488e4aa1d078ff5776b58f66bd29d628fa59adcb2047f4efd3ecb2bd41a71"}, + {file = "orjson-3.9.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6ce2062c4af43b92b0221ed4f445632c6bf4213f8a7da5396a122931377acd9"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:950951799967558c214cd6cceb7ceceed6f81d2c3c4135ee4a2c9c69f58aa225"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2dfaf71499d6fd4153f5c86eebb68e3ec1bf95851b030a4b55c7637a37bbdee4"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:659a8d7279e46c97661839035a1a218b61957316bf0202674e944ac5cfe7ed83"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af17fa87bccad0b7f6fd8ac8f9cbc9ee656b4552783b10b97a071337616db3e4"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd52dec9eddf4c8c74392f3fd52fa137b5f2e2bed1d9ae958d879de5f7d7cded"}, + {file = "orjson-3.9.12-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:640e2b5d8e36b970202cfd0799d11a9a4ab46cf9212332cd642101ec952df7c8"}, + {file = "orjson-3.9.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:daa438bd8024e03bcea2c5a92cd719a663a58e223fba967296b6ab9992259dbf"}, + {file = "orjson-3.9.12-cp312-none-win_amd64.whl", hash = "sha256:1bb8f657c39ecdb924d02e809f992c9aafeb1ad70127d53fb573a6a6ab59d549"}, + {file = "orjson-3.9.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f4098c7674901402c86ba6045a551a2ee345f9f7ed54eeffc7d86d155c8427e5"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5586a533998267458fad3a457d6f3cdbddbcce696c916599fa8e2a10a89b24d3"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54071b7398cd3f90e4bb61df46705ee96cb5e33e53fc0b2f47dbd9b000e238e1"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:67426651faa671b40443ea6f03065f9c8e22272b62fa23238b3efdacd301df31"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a0cd56e8ee56b203abae7d482ac0d233dbfb436bb2e2d5cbcb539fe1200a312"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a84a0c3d4841a42e2571b1c1ead20a83e2792644c5827a606c50fc8af7ca4bee"}, + {file = "orjson-3.9.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:09d60450cda3fa6c8ed17770c3a88473a16460cd0ff2ba74ef0df663b6fd3bb8"}, + {file = "orjson-3.9.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bc82a4db9934a78ade211cf2e07161e4f068a461c1796465d10069cb50b32a80"}, + {file = "orjson-3.9.12-cp38-none-win32.whl", hash = "sha256:61563d5d3b0019804d782137a4f32c72dc44c84e7d078b89d2d2a1adbaa47b52"}, + {file = "orjson-3.9.12-cp38-none-win_amd64.whl", hash = "sha256:410f24309fbbaa2fab776e3212a81b96a1ec6037259359a32ea79fbccfcf76aa"}, + {file = "orjson-3.9.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e773f251258dd82795fd5daeac081d00b97bacf1548e44e71245543374874bcf"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b159baecfda51c840a619948c25817d37733a4d9877fea96590ef8606468b362"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:975e72e81a249174840d5a8df977d067b0183ef1560a32998be340f7e195c730"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06e42e899dde61eb1851a9fad7f1a21b8e4be063438399b63c07839b57668f6c"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c157e999e5694475a5515942aebeed6e43f7a1ed52267c1c93dcfde7d78d421"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dde1bc7c035f2d03aa49dc8642d9c6c9b1a81f2470e02055e76ed8853cfae0c3"}, + {file = "orjson-3.9.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b0e9d73cdbdad76a53a48f563447e0e1ce34bcecef4614eb4b146383e6e7d8c9"}, + {file = "orjson-3.9.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:96e44b21fe407b8ed48afbb3721f3c8c8ce17e345fbe232bd4651ace7317782d"}, + {file = "orjson-3.9.12-cp39-none-win32.whl", hash = "sha256:cbd0f3555205bf2a60f8812133f2452d498dbefa14423ba90fe89f32276f7abf"}, + {file = "orjson-3.9.12-cp39-none-win_amd64.whl", hash = "sha256:03ea7ee7e992532c2f4a06edd7ee1553f0644790553a118e003e3c405add41fa"}, + {file = "orjson-3.9.12.tar.gz", hash = "sha256:da908d23a3b3243632b523344403b128722a5f45e278a8343c2bb67538dff0e4"}, ] [[package]] @@ -1317,6 +1334,108 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "regex" +version = "2023.12.25" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1536,6 +1655,58 @@ files = [ [package.extras] doc = ["reno", "sphinx", "tornado (>=4.5)"] +[[package]] +name = "tiktoken" +version = "0.5.2" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c4e654282ef05ec1bd06ead22141a9a1687991cef2c6a81bdd1284301abc71d"}, + {file = "tiktoken-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b3134aa24319f42c27718c6967f3c1916a38a715a0fa73d33717ba121231307"}, + {file = "tiktoken-0.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6092e6e77730929c8c6a51bb0d7cfdf1b72b63c4d033d6258d1f2ee81052e9e5"}, + {file = "tiktoken-0.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ad8ae2a747622efae75837abba59be6c15a8f31b4ac3c6156bc56ec7a8e631"}, + {file = "tiktoken-0.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51cba7c8711afa0b885445f0637f0fcc366740798c40b981f08c5f984e02c9d1"}, + {file = "tiktoken-0.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3d8c7d2c9313f8e92e987d585ee2ba0f7c40a0de84f4805b093b634f792124f5"}, + {file = "tiktoken-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:692eca18c5fd8d1e0dde767f895c17686faaa102f37640e884eecb6854e7cca7"}, + {file = "tiktoken-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:138d173abbf1ec75863ad68ca289d4da30caa3245f3c8d4bfb274c4d629a2f77"}, + {file = "tiktoken-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7388fdd684690973fdc450b47dfd24d7f0cbe658f58a576169baef5ae4658607"}, + {file = "tiktoken-0.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a114391790113bcff670c70c24e166a841f7ea8f47ee2fe0e71e08b49d0bf2d4"}, + {file = "tiktoken-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca96f001e69f6859dd52926d950cfcc610480e920e576183497ab954e645e6ac"}, + {file = "tiktoken-0.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:15fed1dd88e30dfadcdd8e53a8927f04e1f6f81ad08a5ca824858a593ab476c7"}, + {file = "tiktoken-0.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f8e692db5756f7ea8cb0cfca34638316dcf0841fb8469de8ed7f6a015ba0b0"}, + {file = "tiktoken-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:bcae1c4c92df2ffc4fe9f475bf8148dbb0ee2404743168bbeb9dcc4b79dc1fdd"}, + {file = "tiktoken-0.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b76a1e17d4eb4357d00f0622d9a48ffbb23401dcf36f9716d9bd9c8e79d421aa"}, + {file = "tiktoken-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01d8b171bb5df4035580bc26d4f5339a6fd58d06f069091899d4a798ea279d3e"}, + {file = "tiktoken-0.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42adf7d4fb1ed8de6e0ff2e794a6a15005f056a0d83d22d1d6755a39bffd9e7f"}, + {file = "tiktoken-0.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3f894dbe0adb44609f3d532b8ea10820d61fdcb288b325a458dfc60fefb7db"}, + {file = "tiktoken-0.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:58ccfddb4e62f0df974e8f7e34a667981d9bb553a811256e617731bf1d007d19"}, + {file = "tiktoken-0.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58902a8bad2de4268c2a701f1c844d22bfa3cbcc485b10e8e3e28a050179330b"}, + {file = "tiktoken-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:5e39257826d0647fcac403d8fa0a474b30d02ec8ffc012cfaf13083e9b5e82c5"}, + {file = "tiktoken-0.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bde3b0fbf09a23072d39c1ede0e0821f759b4fa254a5f00078909158e90ae1f"}, + {file = "tiktoken-0.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2ddee082dcf1231ccf3a591d234935e6acf3e82ee28521fe99af9630bc8d2a60"}, + {file = "tiktoken-0.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35c057a6a4e777b5966a7540481a75a31429fc1cb4c9da87b71c8b75b5143037"}, + {file = "tiktoken-0.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c4a049b87e28f1dc60509f8eb7790bc8d11f9a70d99b9dd18dfdd81a084ffe6"}, + {file = "tiktoken-0.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5bf5ce759089f4f6521ea6ed89d8f988f7b396e9f4afb503b945f5c949c6bec2"}, + {file = "tiktoken-0.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0c964f554af1a96884e01188f480dad3fc224c4bbcf7af75d4b74c4b74ae0125"}, + {file = "tiktoken-0.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:368dd5726d2e8788e47ea04f32e20f72a2012a8a67af5b0b003d1e059f1d30a3"}, + {file = "tiktoken-0.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2deef9115b8cd55536c0a02c0203512f8deb2447f41585e6d929a0b878a0dd2"}, + {file = "tiktoken-0.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ed7d380195affbf886e2f8b92b14edfe13f4768ff5fc8de315adba5b773815e"}, + {file = "tiktoken-0.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76fce01309c8140ffe15eb34ded2bb94789614b7d1d09e206838fc173776a18"}, + {file = "tiktoken-0.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60a5654d6a2e2d152637dd9a880b4482267dfc8a86ccf3ab1cec31a8c76bfae8"}, + {file = "tiktoken-0.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41d4d3228e051b779245a8ddd21d4336f8975563e92375662f42d05a19bdff41"}, + {file = "tiktoken-0.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c1cdec2c92fcde8c17a50814b525ae6a88e8e5b02030dc120b76e11db93f13"}, + {file = "tiktoken-0.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:84ddb36faedb448a50b246e13d1b6ee3437f60b7169b723a4b2abad75e914f3e"}, + {file = "tiktoken-0.5.2.tar.gz", hash = "sha256:f54c581f134a8ea96ce2023ab221d4d4d81ab614efa0b2fbce926387deb56c80"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + [[package]] name = "tomlkit" version = "0.12.3" @@ -1758,4 +1929,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "3f7a7f25c788600b31875592b8eff1e6e61cf80fe7c2de4e72e95bbcf6e8059a" +content-hash = "3c41ca809b411be94a980dfc4aa899622efabf5956dfca5a5b6fe63ec0db6ed7" diff --git a/templates/retrieval-agent/pyproject.toml b/templates/retrieval-agent/pyproject.toml index bc0ccff045538..931f743b5d52f 100644 --- a/templates/retrieval-agent/pyproject.toml +++ b/templates/retrieval-agent/pyproject.toml @@ -10,9 +10,10 @@ python = ">=3.8.1,<4.0" langchain = "^0.1" openai = "<2" arxiv = "^2.0.0" +langchain-openai = "^0.0.2.post1" [tool.poetry.group.dev.dependencies] -langchain-cli = ">=0.0.4" +langchain-cli = ">=0.0.20" fastapi = "^0.104.0" sse-starlette = "^1.6.5" diff --git a/templates/retrieval-agent/retrieval_agent/chain.py b/templates/retrieval-agent/retrieval_agent/chain.py index 8b4d215373797..2f774f6bfe30a 100644 --- a/templates/retrieval-agent/retrieval_agent/chain.py +++ b/templates/retrieval-agent/retrieval_agent/chain.py @@ -7,12 +7,12 @@ from langchain.callbacks.manager import CallbackManagerForRetrieverRun from langchain.schema import BaseRetriever, Document from langchain.tools.retriever import create_retriever_tool -from langchain_community.chat_models import AzureChatOpenAI from langchain_community.tools.convert_to_openai import format_tool_to_openai_function from langchain_community.utilities.arxiv import ArxivAPIWrapper from langchain_core.messages import AIMessage, HumanMessage from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.pydantic_v1 import BaseModel, Field +from langchain_openai import AzureChatOpenAI class ArxivRetriever(BaseRetriever, ArxivAPIWrapper): @@ -67,10 +67,9 @@ def _get_relevant_documents( tools = [arxiv_tool] llm = AzureChatOpenAI( temperature=0, - deployment_name=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"], - openai_api_base=os.environ["AZURE_OPENAI_API_BASE"], - openai_api_version=os.environ["AZURE_OPENAI_API_VERSION"], - openai_api_key=os.environ["AZURE_OPENAI_API_KEY"], + azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), + api_key=os.getenv("AZURE_OPENAI_API_KEY"), + api_version=os.getenv("AZURE_OPENAI_API_VERSION"), ) assistant_system_message = """You are a helpful research assistant. \ Lookup relevant information as needed.""" From afb25eeec43472e6611924f2bd4915a74526b283 Mon Sep 17 00:00:00 2001 From: Erick Friis <erick@langchain.dev> Date: Tue, 23 Jan 2024 16:09:16 -0700 Subject: [PATCH 189/215] cli[patch]: add integration tests to default makefile (#16479) --- libs/cli/langchain_cli/integration_template/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/cli/langchain_cli/integration_template/Makefile b/libs/cli/langchain_cli/integration_template/Makefile index cf748963e2263..bed6f5bda53a8 100644 --- a/libs/cli/langchain_cli/integration_template/Makefile +++ b/libs/cli/langchain_cli/integration_template/Makefile @@ -6,7 +6,9 @@ all: help # Define a variable for the test file path. TEST_FILE ?= tests/unit_tests/ -test: +integration_tests: TEST_FILE = tests/integration_tests/ + +test integration_tests: poetry run pytest $(TEST_FILE) tests: From e529939c54d985f24996f600bfa39f25a9c09e8e Mon Sep 17 00:00:00 2001 From: Massimiliano Pronesti <massimiliano.pronesti@gmail.com> Date: Wed, 24 Jan 2024 01:48:56 +0100 Subject: [PATCH 190/215] feat(llms): support more tasks in HuggingFaceHub LLM and remove deprecated dep (#14406) - **Description:** this PR upgrades the `HuggingFaceHub` LLM: * support more tasks (`translation` and `conversational`) * replaced the deprecated `InferenceApi` with `InferenceClient` * adjusted the overall logic to use the "recommended" model for each task when no model is provided, and vice-versa. - **Tag mainter(s)**: @baskaryan @hwchase17 --- .../llms/huggingface_hub.py | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/libs/community/langchain_community/llms/huggingface_hub.py b/libs/community/langchain_community/llms/huggingface_hub.py index 10eb8eb7a0464..f432727773121 100644 --- a/libs/community/langchain_community/llms/huggingface_hub.py +++ b/libs/community/langchain_community/llms/huggingface_hub.py @@ -1,3 +1,4 @@ +import json from typing import Any, Dict, List, Mapping, Optional from langchain_core.callbacks import CallbackManagerForLLMRun @@ -7,8 +8,15 @@ from langchain_community.llms.utils import enforce_stop_tokens -DEFAULT_REPO_ID = "gpt2" -VALID_TASKS = ("text2text-generation", "text-generation", "summarization") +# key: task +# value: key in the output dictionary +VALID_TASKS_DICT = { + "translation": "translation_text", + "summarization": "summary_text", + "conversational": "generated_text", + "text-generation": "generated_text", + "text2text-generation": "generated_text", +} class HuggingFaceHub(LLM): @@ -18,7 +26,8 @@ class HuggingFaceHub(LLM): environment variable ``HUGGINGFACEHUB_API_TOKEN`` set with your API token, or pass it as a named parameter to the constructor. - Only supports `text-generation`, `text2text-generation` and `summarization` for now. + Supports `text-generation`, `text2text-generation`, `conversational`, `translation`, + and `summarization`. Example: .. code-block:: python @@ -28,11 +37,13 @@ class HuggingFaceHub(LLM): """ client: Any #: :meta private: - repo_id: str = DEFAULT_REPO_ID - """Model name to use.""" + repo_id: Optional[str] = None + """Model name to use. + If not provided, the default model for the chosen task will be used.""" task: Optional[str] = None """Task to call the model with. - Should be a task that returns `generated_text` or `summary_text`.""" + Should be a task that returns `generated_text`, `summary_text`, + or `translation_text`.""" model_kwargs: Optional[dict] = None """Keyword arguments to pass to the model.""" @@ -50,18 +61,27 @@ def validate_environment(cls, values: Dict) -> Dict: values, "huggingfacehub_api_token", "HUGGINGFACEHUB_API_TOKEN" ) try: - from huggingface_hub.inference_api import InferenceApi + from huggingface_hub import HfApi, InferenceClient repo_id = values["repo_id"] - client = InferenceApi( - repo_id=repo_id, + client = InferenceClient( + model=repo_id, token=huggingfacehub_api_token, - task=values.get("task"), ) - if client.task not in VALID_TASKS: + if not values["task"]: + if not repo_id: + raise ValueError( + "Must specify either `repo_id` or `task`, or both." + ) + # Use the recommended task for the chosen model + model_info = HfApi(token=huggingfacehub_api_token).model_info( + repo_id=repo_id + ) + values["task"] = model_info.pipeline_tag + if values["task"] not in VALID_TASKS_DICT: raise ValueError( - f"Got invalid task {client.task}, " - f"currently only {VALID_TASKS} are supported" + f"Got invalid task {values['task']}, " + f"currently only {VALID_TASKS_DICT.keys()} are supported" ) values["client"] = client except ImportError: @@ -108,23 +128,20 @@ def _call( """ _model_kwargs = self.model_kwargs or {} params = {**_model_kwargs, **kwargs} - response = self.client(inputs=prompt, params=params) + + response = self.client.post( + json={"inputs": prompt, "params": params}, task=self.task + ) + response = json.loads(response.decode()) if "error" in response: raise ValueError(f"Error raised by inference API: {response['error']}") - if self.client.task == "text-generation": - # Text generation sometimes return includes the starter text. - text = response[0]["generated_text"] - if text.startswith(prompt): - text = response[0]["generated_text"][len(prompt) :] - elif self.client.task == "text2text-generation": - text = response[0]["generated_text"] - elif self.client.task == "summarization": - text = response[0]["summary_text"] + + response_key = VALID_TASKS_DICT[self.task] # type: ignore + if isinstance(response, list): + text = response[0][response_key] else: - raise ValueError( - f"Got invalid task {self.client.task}, " - f"currently only {VALID_TASKS} are supported" - ) + text = response[response_key] + if stop is not None: # This is a bit hacky, but I can't figure out a better way to enforce # stop tokens when making calls to huggingface_hub. From 26b2ad6d5b708176f73d4797099d66c4265036db Mon Sep 17 00:00:00 2001 From: dudgeon <dudgeon@gmail.com> Date: Tue, 23 Jan 2024 19:50:13 -0500 Subject: [PATCH 191/215] Fixed typo on quickstart.ipynb (#16482) - **Description:** Quick typo fix: `inpect` >> `inspect` - **Issue:** N/A - **Dependencies:** any dependencies required for this change, - **Twitter handle:** @geoffdudgeon --- docs/docs/use_cases/sql/quickstart.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use_cases/sql/quickstart.ipynb b/docs/docs/use_cases/sql/quickstart.ipynb index 490700a45197b..4a0ee60f3ed3a 100644 --- a/docs/docs/use_cases/sql/quickstart.ipynb +++ b/docs/docs/use_cases/sql/quickstart.ipynb @@ -189,7 +189,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can look at the [LangSmith trace](https://smith.langchain.com/public/c8fa52ea-be46-4829-bde2-52894970b830/r) to get a better understanding of what this chain is doing. We can also inpect the chain directly for its prompts. Looking at the prompt (below), we can see that it is:\n", + "We can look at the [LangSmith trace](https://smith.langchain.com/public/c8fa52ea-be46-4829-bde2-52894970b830/r) to get a better understanding of what this chain is doing. We can also inspect the chain directly for its prompts. Looking at the prompt (below), we can see that it is:\n", "\n", "* Dialect-specific. In this case it references SQLite explicitly.\n", "* Has definitions for all the available tables.\n", From cfc225ecb3f442b06f552d7eff2ec73b39f9beb5 Mon Sep 17 00:00:00 2001 From: gcheron <guilhem.cheron35@gmail.com> Date: Wed, 24 Jan 2024 01:50:48 +0100 Subject: [PATCH 192/215] community: SQLStrStore/SQLDocStore provide an easy SQL alternative to `InMemoryStore` to persist data remotely in a SQL storage (#15909) **Description:** - Implement `SQLStrStore` and `SQLDocStore` classes that inherits from `BaseStore` to allow to persist data remotely on a SQL server. - SQL is widely used and sometimes we do not want to install a caching solution like Redis. - Multiple issues/comments complain that there is no easy remote and persistent solution that are not in memory (users want to replace InMemoryStore), e.g., https://github.com/langchain-ai/langchain/issues/14267, https://github.com/langchain-ai/langchain/issues/15633, https://github.com/langchain-ai/langchain/issues/14643, https://stackoverflow.com/questions/77385587/persist-parentdocumentretriever-of-langchain - This is particularly painful when wanting to use `ParentDocumentRetriever ` - This implementation is particularly useful when: * it's expensive to construct an InMemoryDocstore/dict * you want to retrieve documents from remote sources * you just want to reuse existing objects - This implementation integrates well with PGVector, indeed, when using PGVector, you already have a SQL instance running. `SQLDocStore` is a convenient way of using this instance to store documents associated to vectors. An integration example with ParentDocumentRetriever and PGVector is provided in docs/docs/integrations/stores/sql.ipynb or [here](https://github.com/gcheron/langchain/blob/sql-store/docs/docs/integrations/stores/sql.ipynb). - It persists `str` and `Document` objects but can be easily extended. **Issue:** Provide an easy SQL alternative to `InMemoryStore`. --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- docs/docs/integrations/stores/sql.ipynb | 186 ++++++++++ .../langchain_community/storage/__init__.py | 6 + .../langchain_community/storage/sql.py | 345 ++++++++++++++++++ .../integration_tests/storage/test_sql.py | 228 ++++++++++++ .../tests/unit_tests/storage/test_imports.py | 2 + .../tests/unit_tests/storage/test_sql.py | 7 + 6 files changed, 774 insertions(+) create mode 100644 docs/docs/integrations/stores/sql.ipynb create mode 100644 libs/community/langchain_community/storage/sql.py create mode 100644 libs/community/tests/integration_tests/storage/test_sql.py create mode 100644 libs/community/tests/unit_tests/storage/test_sql.py diff --git a/docs/docs/integrations/stores/sql.ipynb b/docs/docs/integrations/stores/sql.ipynb new file mode 100644 index 0000000000000..ecb2f472a8fc1 --- /dev/null +++ b/docs/docs/integrations/stores/sql.ipynb @@ -0,0 +1,186 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: SQL\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SQLStore\n", + "\n", + "The `SQLStrStore` and `SQLDocStore` implement remote data access and persistence to store strings or LangChain documents in your SQL instance." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['value1', 'value2']\n", + "['key2']\n", + "['key2']\n" + ] + } + ], + "source": [ + "from langchain_community.storage import SQLStrStore\n", + "\n", + "# simple example using an SQLStrStore to store strings\n", + "# same as you would use in \"InMemoryStore\" but using SQL persistence\n", + "CONNECTION_STRING = \"postgresql+psycopg2://user:pass@localhost:5432/db\"\n", + "COLLECTION_NAME = \"test_collection\"\n", + "\n", + "store = SQLStrStore(\n", + " collection_name=COLLECTION_NAME,\n", + " connection_string=CONNECTION_STRING,\n", + ")\n", + "store.mset([(\"key1\", \"value1\"), (\"key2\", \"value2\")])\n", + "print(store.mget([\"key1\", \"key2\"]))\n", + "# ['value1', 'value2']\n", + "store.mdelete([\"key1\"])\n", + "print(list(store.yield_keys()))\n", + "# ['key2']\n", + "print(list(store.yield_keys(prefix=\"k\")))\n", + "# ['key2']\n", + "# delete the COLLECTION_NAME collection" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration with ParentRetriever and PGVector\n", + "\n", + "When using PGVector, you already have a SQL instance running. Here is a convenient way of using this instance to store documents associated to vectors. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Prepare the PGVector vectorestore with something like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import PGVector\n", + "from langchain_openai import OpenAIEmbeddings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "embeddings = OpenAIEmbeddings()\n", + "vector_db = PGVector.from_existing_index(\n", + " embedding=embeddings,\n", + " collection_name=COLLECTION_NAME,\n", + " connection_string=CONNECTION_STRING,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then create the parent retiever using `SQLDocStore` to persist the documents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.document_loaders import TextLoader\n", + "from langchain.retrievers import ParentDocumentRetriever\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_community.storage import SQLDocStore\n", + "\n", + "CONNECTION_STRING = \"postgresql+psycopg2://user:pass@localhost:5432/db\"\n", + "COLLECTION_NAME = \"state_of_the_union_test\"\n", + "docstore = SQLDocStore(\n", + " collection_name=COLLECTION_NAME,\n", + " connection_string=CONNECTION_STRING,\n", + ")\n", + "\n", + "loader = TextLoader(\"./state_of_the_union.txt\")\n", + "documents = loader.load()\n", + "\n", + "parent_splitter = RecursiveCharacterTextSplitter(chunk_size=400)\n", + "child_splitter = RecursiveCharacterTextSplitter(chunk_size=50)\n", + "\n", + "retriever = ParentDocumentRetriever(\n", + " vectorstore=vector_db,\n", + " docstore=docstore,\n", + " child_splitter=child_splitter,\n", + " parent_splitter=parent_splitter,\n", + ")\n", + "retriever.add_documents(documents)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Delete a collection" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.storage import SQLStrStore\n", + "\n", + "# delete the COLLECTION_NAME collection\n", + "CONNECTION_STRING = \"postgresql+psycopg2://user:pass@localhost:5432/db\"\n", + "COLLECTION_NAME = \"test_collection\"\n", + "store = SQLStrStore(\n", + " collection_name=COLLECTION_NAME,\n", + " connection_string=CONNECTION_STRING,\n", + ")\n", + "store.delete_collection()" + ] + } + ], + "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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/libs/community/langchain_community/storage/__init__.py b/libs/community/langchain_community/storage/__init__.py index 494591b03c713..5c28015e57437 100644 --- a/libs/community/langchain_community/storage/__init__.py +++ b/libs/community/langchain_community/storage/__init__.py @@ -11,6 +11,10 @@ AstraDBStore, ) from langchain_community.storage.redis import RedisStore +from langchain_community.storage.sql import ( + SQLDocStore, + SQLStrStore, +) from langchain_community.storage.upstash_redis import ( UpstashRedisByteStore, UpstashRedisStore, @@ -22,4 +26,6 @@ "RedisStore", "UpstashRedisByteStore", "UpstashRedisStore", + "SQLDocStore", + "SQLStrStore", ] diff --git a/libs/community/langchain_community/storage/sql.py b/libs/community/langchain_community/storage/sql.py new file mode 100644 index 0000000000000..7baaf64285936 --- /dev/null +++ b/libs/community/langchain_community/storage/sql.py @@ -0,0 +1,345 @@ +"""SQL storage that persists data in a SQL database +and supports data isolation using collections.""" +from __future__ import annotations + +import uuid +from typing import Any, Generic, Iterator, List, Optional, Sequence, Tuple, TypeVar + +import sqlalchemy +from sqlalchemy import JSON, UUID +from sqlalchemy.orm import Session, relationship + +try: + from sqlalchemy.orm import declarative_base +except ImportError: + from sqlalchemy.ext.declarative import declarative_base + +from langchain_core.documents import Document +from langchain_core.load import Serializable, dumps, loads +from langchain_core.stores import BaseStore + +V = TypeVar("V") + +ITERATOR_WINDOW_SIZE = 1000 + +Base = declarative_base() # type: Any + + +_LANGCHAIN_DEFAULT_COLLECTION_NAME = "langchain" + + +class BaseModel(Base): + """Base model for the SQL stores.""" + + __abstract__ = True + uuid = sqlalchemy.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + + +_classes: Any = None + + +def _get_storage_stores() -> Any: + global _classes + if _classes is not None: + return _classes + + class CollectionStore(BaseModel): + """Collection store.""" + + __tablename__ = "langchain_storage_collection" + + name = sqlalchemy.Column(sqlalchemy.String) + cmetadata = sqlalchemy.Column(JSON) + + items = relationship( + "ItemStore", + back_populates="collection", + passive_deletes=True, + ) + + @classmethod + def get_by_name( + cls, session: Session, name: str + ) -> Optional["CollectionStore"]: + # type: ignore + return session.query(cls).filter(cls.name == name).first() + + @classmethod + def get_or_create( + cls, + session: Session, + name: str, + cmetadata: Optional[dict] = None, + ) -> Tuple["CollectionStore", bool]: + """ + Get or create a collection. + Returns [Collection, bool] where the bool is True if the collection was created. + """ # noqa: E501 + created = False + collection = cls.get_by_name(session, name) + if collection: + return collection, created + + collection = cls(name=name, cmetadata=cmetadata) + session.add(collection) + session.commit() + created = True + return collection, created + + class ItemStore(BaseModel): + """Item store.""" + + __tablename__ = "langchain_storage_items" + + collection_id = sqlalchemy.Column( + UUID(as_uuid=True), + sqlalchemy.ForeignKey( + f"{CollectionStore.__tablename__}.uuid", + ondelete="CASCADE", + ), + ) + collection = relationship(CollectionStore, back_populates="items") + + content = sqlalchemy.Column(sqlalchemy.String, nullable=True) + + # custom_id : any user defined id + custom_id = sqlalchemy.Column(sqlalchemy.String, nullable=True) + + _classes = (ItemStore, CollectionStore) + + return _classes + + +class SQLBaseStore(BaseStore[str, V], Generic[V]): + """SQL storage + + Args: + connection_string: SQL connection string that will be passed to SQLAlchemy. + collection_name: The name of the collection to use. (default: langchain) + NOTE: Collections are useful to isolate your data in a given a database. + This is not the name of the table, but the name of the collection. + The tables will be created when initializing the store (if not exists) + So, make sure the user has the right permissions to create tables. + pre_delete_collection: If True, will delete the collection if it exists. + (default: False). Useful for testing. + engine_args: SQLAlchemy's create engine arguments. + + Example: + .. code-block:: python + + from langchain_community.storage import SQLDocStore + from langchain_community.embeddings.openai import OpenAIEmbeddings + + # example using an SQLDocStore to store Document objects for + # a ParentDocumentRetriever + CONNECTION_STRING = "postgresql+psycopg2://user:pass@localhost:5432/db" + COLLECTION_NAME = "state_of_the_union_test" + docstore = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + ) + child_splitter = RecursiveCharacterTextSplitter(chunk_size=400) + vectorstore = ... + + retriever = ParentDocumentRetriever( + vectorstore=vectorstore, + docstore=docstore, + child_splitter=child_splitter, + ) + + # example using an SQLStrStore to store strings + # same example as in "InMemoryStore" but using SQL persistence + store = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + ) + store.mset([('key1', 'value1'), ('key2', 'value2')]) + store.mget(['key1', 'key2']) + # ['value1', 'value2'] + store.mdelete(['key1']) + list(store.yield_keys()) + # ['key2'] + list(store.yield_keys(prefix='k')) + # ['key2'] + + # delete the COLLECTION_NAME collection + docstore.delete_collection() + """ + + def __init__( + self, + connection_string: str, + collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME, + collection_metadata: Optional[dict] = None, + pre_delete_collection: bool = False, + connection: Optional[sqlalchemy.engine.Connection] = None, + engine_args: Optional[dict[str, Any]] = None, + ) -> None: + self.connection_string = connection_string + self.collection_name = collection_name + self.collection_metadata = collection_metadata + self.pre_delete_collection = pre_delete_collection + self.engine_args = engine_args or {} + # Create a connection if not provided, otherwise use the provided connection + self._conn = connection if connection else self.__connect() + self.__post_init__() + + def __post_init__( + self, + ) -> None: + """Initialize the store.""" + ItemStore, CollectionStore = _get_storage_stores() + self.CollectionStore = CollectionStore + self.ItemStore = ItemStore + self.__create_tables_if_not_exists() + self.__create_collection() + + def __connect(self) -> sqlalchemy.engine.Connection: + engine = sqlalchemy.create_engine(self.connection_string, **self.engine_args) + conn = engine.connect() + return conn + + def __create_tables_if_not_exists(self) -> None: + with self._conn.begin(): + Base.metadata.create_all(self._conn) + + def __create_collection(self) -> None: + if self.pre_delete_collection: + self.delete_collection() + with Session(self._conn) as session: + self.CollectionStore.get_or_create( + session, self.collection_name, cmetadata=self.collection_metadata + ) + + def delete_collection(self) -> None: + with Session(self._conn) as session: + collection = self.__get_collection(session) + if not collection: + return + session.delete(collection) + session.commit() + + def __get_collection(self, session: Session) -> Any: + return self.CollectionStore.get_by_name(session, self.collection_name) + + def __del__(self) -> None: + if self._conn: + self._conn.close() + + def __serialize_value(self, obj: V) -> str: + if isinstance(obj, Serializable): + return dumps(obj) + return obj + + def __deserialize_value(self, obj: V) -> str: + try: + return loads(obj) + except Exception: + return obj + + def mget(self, keys: Sequence[str]) -> List[Optional[V]]: + """Get the values associated with the given keys. + + Args: + keys (Sequence[str]): A sequence of keys. + + Returns: + A sequence of optional values associated with the keys. + If a key is not found, the corresponding value will be None. + """ + with Session(self._conn) as session: + collection = self.__get_collection(session) + + items = ( + session.query(self.ItemStore.content, self.ItemStore.custom_id) + .where( + sqlalchemy.and_( + self.ItemStore.custom_id.in_(keys), + self.ItemStore.collection_id == (collection.uuid), + ) + ) + .all() + ) + + ordered_values = {key: None for key in keys} + for item in items: + v = item[0] + val = self.__deserialize_value(v) if v is not None else v + k = item[1] + ordered_values[k] = val + + return [ordered_values[key] for key in keys] + + def mset(self, key_value_pairs: Sequence[Tuple[str, V]]) -> None: + """Set the values for the given keys. + + Args: + key_value_pairs (Sequence[Tuple[str, V]]): A sequence of key-value pairs. + + Returns: + None + """ + with Session(self._conn) as session: + collection = self.__get_collection(session) + if not collection: + raise ValueError("Collection not found") + for id, item in key_value_pairs: + content = self.__serialize_value(item) + item_store = self.ItemStore( + content=content, + custom_id=id, + collection_id=collection.uuid, + ) + session.add(item_store) + session.commit() + + def mdelete(self, keys: Sequence[str]) -> None: + """Delete the given keys and their associated values. + + Args: + keys (Sequence[str]): A sequence of keys to delete. + """ + with Session(self._conn) as session: + collection = self.__get_collection(session) + if not collection: + raise ValueError("Collection not found") + if keys is not None: + stmt = sqlalchemy.delete(self.ItemStore).where( + sqlalchemy.and_( + self.ItemStore.custom_id.in_(keys), + self.ItemStore.collection_id == (collection.uuid), + ) + ) + session.execute(stmt) + session.commit() + + def yield_keys(self, prefix: Optional[str] = None) -> Iterator[str]: + """Get an iterator over keys that match the given prefix. + + Args: + prefix (str, optional): The prefix to match. Defaults to None. + + Returns: + Iterator[str]: An iterator over keys that match the given prefix. + """ + with Session(self._conn) as session: + collection = self.__get_collection(session) + start = 0 + while True: + stop = start + ITERATOR_WINDOW_SIZE + query = session.query(self.ItemStore.custom_id).where( + self.ItemStore.collection_id == (collection.uuid) + ) + if prefix is not None: + query = query.filter(self.ItemStore.custom_id.startswith(prefix)) + items = query.slice(start, stop).all() + + if len(items) == 0: + break + for item in items: + yield item[0] + start += ITERATOR_WINDOW_SIZE + + +SQLDocStore = SQLBaseStore[Document] +SQLStrStore = SQLBaseStore[str] diff --git a/libs/community/tests/integration_tests/storage/test_sql.py b/libs/community/tests/integration_tests/storage/test_sql.py new file mode 100644 index 0000000000000..c09b9745c65cd --- /dev/null +++ b/libs/community/tests/integration_tests/storage/test_sql.py @@ -0,0 +1,228 @@ +"""Implement integration tests for SQL storage.""" +import os + +from langchain_core.documents import Document + +from langchain_community.storage.sql import SQLDocStore, SQLStrStore + + +def connection_string_from_db_params() -> str: + """Return connection string from database parameters.""" + dbdriver = os.environ.get("TEST_SQL_DBDRIVER", "postgresql+psycopg2") + host = os.environ.get("TEST_SQL_HOST", "localhost") + port = int(os.environ.get("TEST_SQL_PORT", "5432")) + database = os.environ.get("TEST_SQL_DATABASE", "postgres") + user = os.environ.get("TEST_SQL_USER", "postgres") + password = os.environ.get("TEST_SQL_PASSWORD", "postgres") + return f"{dbdriver}://{user}:{password}@{host}:{port}/{database}" + + +CONNECTION_STRING = connection_string_from_db_params() +COLLECTION_NAME = "test_collection" +COLLECTION_NAME_2 = "test_collection_2" + + +def test_str_store_mget() -> None: + store = SQLStrStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + store.mset([("key1", "value1"), ("key2", "value2")]) + + values = store.mget(["key1", "key2"]) + assert values == ["value1", "value2"] + + # Test non-existent key + non_existent_value = store.mget(["key3"]) + assert non_existent_value == [None] + store.delete_collection() + + +def test_str_store_mset() -> None: + store = SQLStrStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + store.mset([("key1", "value1"), ("key2", "value2")]) + + values = store.mget(["key1", "key2"]) + assert values == ["value1", "value2"] + store.delete_collection() + + +def test_str_store_mdelete() -> None: + store = SQLStrStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + store.mset([("key1", "value1"), ("key2", "value2")]) + + store.mdelete(["key1"]) + + values = store.mget(["key1", "key2"]) + assert values == [None, "value2"] + + # Test deleting non-existent key + store.mdelete(["key3"]) # No error should be raised + store.delete_collection() + + +def test_str_store_yield_keys() -> None: + store = SQLStrStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + store.mset([("key1", "value1"), ("key2", "value2"), ("key3", "value3")]) + + keys = list(store.yield_keys()) + assert set(keys) == {"key1", "key2", "key3"} + + keys_with_prefix = list(store.yield_keys(prefix="key")) + assert set(keys_with_prefix) == {"key1", "key2", "key3"} + + keys_with_invalid_prefix = list(store.yield_keys(prefix="x")) + assert keys_with_invalid_prefix == [] + store.delete_collection() + + +def test_str_store_collection() -> None: + """Test that collections are isolated within a db.""" + store_1 = SQLStrStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + store_2 = SQLStrStore( + collection_name=COLLECTION_NAME_2, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + + store_1.mset([("key1", "value1"), ("key2", "value2")]) + store_2.mset([("key3", "value3"), ("key4", "value4")]) + + values = store_1.mget(["key1", "key2"]) + assert values == ["value1", "value2"] + values = store_1.mget(["key3", "key4"]) + assert values == [None, None] + + values = store_2.mget(["key1", "key2"]) + assert values == [None, None] + values = store_2.mget(["key3", "key4"]) + assert values == ["value3", "value4"] + + store_1.delete_collection() + store_2.delete_collection() + + +def test_doc_store_mget() -> None: + store = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + doc_1 = Document(page_content="value1") + doc_2 = Document(page_content="value2") + store.mset([("key1", doc_1), ("key2", doc_2)]) + + values = store.mget(["key1", "key2"]) + assert values == [doc_1, doc_2] + + # Test non-existent key + non_existent_value = store.mget(["key3"]) + assert non_existent_value == [None] + store.delete_collection() + + +def test_doc_store_mset() -> None: + store = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + doc_1 = Document(page_content="value1") + doc_2 = Document(page_content="value2") + store.mset([("key1", doc_1), ("key2", doc_2)]) + + values = store.mget(["key1", "key2"]) + assert values == [doc_1, doc_2] + store.delete_collection() + + +def test_doc_store_mdelete() -> None: + store = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + doc_1 = Document(page_content="value1") + doc_2 = Document(page_content="value2") + store.mset([("key1", doc_1), ("key2", doc_2)]) + + store.mdelete(["key1"]) + + values = store.mget(["key1", "key2"]) + assert values == [None, doc_2] + + # Test deleting non-existent key + store.mdelete(["key3"]) # No error should be raised + store.delete_collection() + + +def test_doc_store_yield_keys() -> None: + store = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + doc_1 = Document(page_content="value1") + doc_2 = Document(page_content="value2") + doc_3 = Document(page_content="value3") + store.mset([("key1", doc_1), ("key2", doc_2), ("key3", doc_3)]) + + keys = list(store.yield_keys()) + assert set(keys) == {"key1", "key2", "key3"} + + keys_with_prefix = list(store.yield_keys(prefix="key")) + assert set(keys_with_prefix) == {"key1", "key2", "key3"} + + keys_with_invalid_prefix = list(store.yield_keys(prefix="x")) + assert keys_with_invalid_prefix == [] + store.delete_collection() + + +def test_doc_store_collection() -> None: + """Test that collections are isolated within a db.""" + store_1 = SQLDocStore( + collection_name=COLLECTION_NAME, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + store_2 = SQLDocStore( + collection_name=COLLECTION_NAME_2, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + doc_1 = Document(page_content="value1") + doc_2 = Document(page_content="value2") + doc_3 = Document(page_content="value3") + doc_4 = Document(page_content="value4") + store_1.mset([("key1", doc_1), ("key2", doc_2)]) + store_2.mset([("key3", doc_3), ("key4", doc_4)]) + + values = store_1.mget(["key1", "key2"]) + assert values == [doc_1, doc_2] + values = store_1.mget(["key3", "key4"]) + assert values == [None, None] + + values = store_2.mget(["key1", "key2"]) + assert values == [None, None] + values = store_2.mget(["key3", "key4"]) + assert values == [doc_3, doc_4] + + store_1.delete_collection() + store_2.delete_collection() diff --git a/libs/community/tests/unit_tests/storage/test_imports.py b/libs/community/tests/unit_tests/storage/test_imports.py index 27bcec56d4b26..0cc4914a7406d 100644 --- a/libs/community/tests/unit_tests/storage/test_imports.py +++ b/libs/community/tests/unit_tests/storage/test_imports.py @@ -6,6 +6,8 @@ "RedisStore", "UpstashRedisByteStore", "UpstashRedisStore", + "SQLDocStore", + "SQLStrStore", ] diff --git a/libs/community/tests/unit_tests/storage/test_sql.py b/libs/community/tests/unit_tests/storage/test_sql.py new file mode 100644 index 0000000000000..1d988414637a8 --- /dev/null +++ b/libs/community/tests/unit_tests/storage/test_sql.py @@ -0,0 +1,7 @@ +"""Light weight unit test that attempts to import SQLDocStore/SQLStrStore. +""" + + +def test_import_storage() -> None: + """Attempt to import storage modules.""" + from langchain_community.storage.sql import SQLDocStore, SQLStrStore # noqa From 20fcd49348063ee7f0d12f6138763d76e125f98c Mon Sep 17 00:00:00 2001 From: baichuan-assistant <139942740+baichuan-assistant@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:01:57 +0800 Subject: [PATCH 193/215] community: Fix Baichuan Chat. (#15207) - **Description:** Baichuan Chat (with both Baichuan-Turbo and Baichuan-Turbo-192K models) has updated their APIs. There are breaking changes. For example, BAICHUAN_SECRET_KEY is removed in the latest API but is still required in Langchain. Baichuan's Langchain integration needs to be updated to the latest version. - **Issue:** #15206 - **Dependencies:** None, - **Twitter handle:** None @hwchase17. Co-authored-by: BaiChuanHelper <wintergyc@WinterGYCs-MacBook-Pro.local> --- docs/docs/integrations/chat/baichuan.ipynb | 12 +-- .../chat_models/baichuan.py | 87 +++++++------------ .../chat_models/test_baichuan.py | 35 ++++++-- .../unit_tests/chat_models/test_baichuan.py | 36 +------- 4 files changed, 68 insertions(+), 102 deletions(-) diff --git a/docs/docs/integrations/chat/baichuan.ipynb b/docs/docs/integrations/chat/baichuan.ipynb index 2724727ad75b1..14f2d0d4c987d 100644 --- a/docs/docs/integrations/chat/baichuan.ipynb +++ b/docs/docs/integrations/chat/baichuan.ipynb @@ -13,7 +13,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# ChatBaichuan\n", + "# Chat with Baichuan-192K\n", "\n", "Baichuan chat models API by Baichuan Intelligent Technology. For more information, see [https://platform.baichuan-ai.com/docs/api](https://platform.baichuan-ai.com/docs/api)" ] @@ -44,19 +44,16 @@ }, "outputs": [], "source": [ - "chat = ChatBaichuan(\n", - " baichuan_api_key=\"YOUR_API_KEY\", baichuan_secret_key=\"YOUR_SECRET_KEY\"\n", - ")" + "chat = ChatBaichuan(baichuan_api_key=\"YOUR_API_KEY\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "or you can set `api_key` and `secret_key` in your environment variables\n", + "or you can set `api_key` in your environment variables\n", "```bash\n", "export BAICHUAN_API_KEY=YOUR_API_KEY\n", - "export BAICHUAN_SECRET_KEY=YOUR_SECRET_KEY\n", "```" ] }, @@ -91,7 +88,7 @@ "collapsed": false }, "source": [ - "## For ChatBaichuan with Streaming" + "## Chat with Baichuan-192K with Streaming" ] }, { @@ -108,7 +105,6 @@ "source": [ "chat = ChatBaichuan(\n", " baichuan_api_key=\"YOUR_API_KEY\",\n", - " baichuan_secret_key=\"YOUR_SECRET_KEY\",\n", " streaming=True,\n", ")" ] diff --git a/libs/community/langchain_community/chat_models/baichuan.py b/libs/community/langchain_community/chat_models/baichuan.py index 14cf4a57e2ee4..d95412739be6b 100644 --- a/libs/community/langchain_community/chat_models/baichuan.py +++ b/libs/community/langchain_community/chat_models/baichuan.py @@ -1,7 +1,5 @@ -import hashlib import json import logging -import time from typing import Any, Dict, Iterator, List, Mapping, Optional, Type import requests @@ -30,7 +28,7 @@ logger = logging.getLogger(__name__) -DEFAULT_API_BASE = "https://api.baichuan-ai.com/v1" +DEFAULT_API_BASE = "https://api.baichuan-ai.com/v1/chat/completions" def _convert_message_to_dict(message: BaseMessage) -> dict: @@ -73,14 +71,6 @@ def _convert_delta_to_message_chunk( return default_class(content=content) -# signature generation -def _signature(secret_key: SecretStr, payload: Dict[str, Any], timestamp: int) -> str: - input_str = secret_key.get_secret_value() + json.dumps(payload) + str(timestamp) - md5 = hashlib.md5() - md5.update(input_str.encode("utf-8")) - return md5.hexdigest() - - class ChatBaichuan(BaseChatModel): """Baichuan chat models API by Baichuan Intelligent Technology. @@ -91,7 +81,6 @@ class ChatBaichuan(BaseChatModel): def lc_secrets(self) -> Dict[str, str]: return { "baichuan_api_key": "BAICHUAN_API_KEY", - "baichuan_secret_key": "BAICHUAN_SECRET_KEY", } @property @@ -103,14 +92,14 @@ def lc_serializable(self) -> bool: baichuan_api_key: Optional[SecretStr] = None """Baichuan API Key""" baichuan_secret_key: Optional[SecretStr] = None - """Baichuan Secret Key""" + """[DEPRECATED, keeping it for for backward compatibility] Baichuan Secret Key""" streaming: bool = False """Whether to stream the results or not.""" request_timeout: int = 60 """request timeout for chat http requests""" - - model = "Baichuan2-53B" - """model name of Baichuan, default is `Baichuan2-53B`.""" + model = "Baichuan2-Turbo-192K" + """model name of Baichuan, default is `Baichuan2-Turbo-192K`, + other options include `Baichuan2-Turbo`""" temperature: float = 0.3 """What sampling temperature to use.""" top_k: int = 5 @@ -168,13 +157,6 @@ def validate_environment(cls, values: Dict) -> Dict: "BAICHUAN_API_KEY", ) ) - values["baichuan_secret_key"] = convert_to_secret_str( - get_from_dict_or_env( - values, - "baichuan_secret_key", - "BAICHUAN_SECRET_KEY", - ) - ) return values @@ -187,6 +169,7 @@ def _default_params(self) -> Dict[str, Any]: "top_p": self.top_p, "top_k": self.top_k, "with_search_enhance": self.with_search_enhance, + "stream": self.streaming, } return {**normal_params, **self.model_kwargs} @@ -205,12 +188,9 @@ def _generate( return generate_from_stream(stream_iter) res = self._chat(messages, **kwargs) - + if res.status_code != 200: + raise ValueError(f"Error from Baichuan api response: {res}") response = res.json() - - if response.get("code") != 0: - raise ValueError(f"Error from Baichuan api response: {response}") - return self._create_chat_result(response) def _stream( @@ -221,43 +201,49 @@ def _stream( **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: res = self._chat(messages, **kwargs) - + if res.status_code != 200: + raise ValueError(f"Error from Baichuan api response: {res}") default_chunk_class = AIMessageChunk for chunk in res.iter_lines(): + chunk = chunk.decode("utf-8").strip("\r\n") + parts = chunk.split("data: ", 1) + chunk = parts[1] if len(parts) > 1 else None + if chunk is None: + continue + if chunk == "[DONE]": + break response = json.loads(chunk) - if response.get("code") != 0: - raise ValueError(f"Error from Baichuan api response: {response}") - - data = response.get("data") - for m in data.get("messages"): - chunk = _convert_delta_to_message_chunk(m, default_chunk_class) + for m in response.get("choices"): + chunk = _convert_delta_to_message_chunk( + m.get("delta"), default_chunk_class + ) default_chunk_class = chunk.__class__ yield ChatGenerationChunk(message=chunk) if run_manager: run_manager.on_llm_new_token(chunk.content) def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> requests.Response: - if self.baichuan_secret_key is None: - raise ValueError("Baichuan secret key is not set.") - parameters = {**self._default_params, **kwargs} model = parameters.pop("model") headers = parameters.pop("headers", {}) + temperature = parameters.pop("temperature", 0.3) + top_k = parameters.pop("top_k", 5) + top_p = parameters.pop("top_p", 0.85) + with_search_enhance = parameters.pop("with_search_enhance", False) + stream = parameters.pop("stream", False) payload = { "model": model, "messages": [_convert_message_to_dict(m) for m in messages], - "parameters": parameters, + "top_k": top_k, + "top_p": top_p, + "temperature": temperature, + "with_search_enhance": with_search_enhance, + "stream": stream, } - timestamp = int(time.time()) - url = self.baichuan_api_base - if self.streaming: - url = f"{url}/stream" - url = f"{url}/chat" - api_key = "" if self.baichuan_api_key: api_key = self.baichuan_api_key.get_secret_value() @@ -268,13 +254,6 @@ def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> requests.Response headers={ "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", - "X-BC-Timestamp": str(timestamp), - "X-BC-Signature": _signature( - secret_key=self.baichuan_secret_key, - payload=payload, - timestamp=timestamp, - ), - "X-BC-Sign-Algo": "MD5", **headers, }, json=payload, @@ -284,8 +263,8 @@ def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> requests.Response def _create_chat_result(self, response: Mapping[str, Any]) -> ChatResult: generations = [] - for m in response["data"]["messages"]: - message = _convert_dict_to_message(m) + for c in response["choices"]: + message = _convert_dict_to_message(c["message"]) gen = ChatGeneration(message=message) generations.append(gen) diff --git a/libs/community/tests/integration_tests/chat_models/test_baichuan.py b/libs/community/tests/integration_tests/chat_models/test_baichuan.py index 0ad3ab74799ca..6caec6003e95d 100644 --- a/libs/community/tests/integration_tests/chat_models/test_baichuan.py +++ b/libs/community/tests/integration_tests/chat_models/test_baichuan.py @@ -2,17 +2,36 @@ from langchain_community.chat_models.baichuan import ChatBaichuan +# For testing, run: +# TEST_FILE=tests/integration_tests/chat_models/test_baichuan.py make test -def test_chat_baichuan() -> None: + +def test_chat_baichuan_default() -> None: + chat = ChatBaichuan(streaming=True) + message = HumanMessage(content="请完整背诵将进酒,背诵5遍") + response = chat([message]) + assert isinstance(response, AIMessage) + assert isinstance(response.content, str) + + +def test_chat_baichuan_default_non_streaming() -> None: chat = ChatBaichuan() + message = HumanMessage(content="请完整背诵将进酒,背诵5遍") + response = chat([message]) + assert isinstance(response, AIMessage) + assert isinstance(response.content, str) + + +def test_chat_baichuan_turbo() -> None: + chat = ChatBaichuan(model="Baichuan2-Turbo", streaming=True) message = HumanMessage(content="Hello") response = chat([message]) assert isinstance(response, AIMessage) assert isinstance(response.content, str) -def test_chat_baichuan_with_model() -> None: - chat = ChatBaichuan(model="Baichuan2-13B") +def test_chat_baichuan_turbo_non_streaming() -> None: + chat = ChatBaichuan(model="Baichuan2-Turbo") message = HumanMessage(content="Hello") response = chat([message]) assert isinstance(response, AIMessage) @@ -20,7 +39,7 @@ def test_chat_baichuan_with_model() -> None: def test_chat_baichuan_with_temperature() -> None: - chat = ChatBaichuan(model="Baichuan2-13B", temperature=1.0) + chat = ChatBaichuan(temperature=1.0) message = HumanMessage(content="Hello") response = chat([message]) assert isinstance(response, AIMessage) @@ -29,13 +48,15 @@ def test_chat_baichuan_with_temperature() -> None: def test_chat_baichuan_with_kwargs() -> None: chat = ChatBaichuan() - message = HumanMessage(content="Hello") - response = chat([message], temperature=0.88, top_p=0.7) + message = HumanMessage(content="百川192K API是什么时候上线的?") + response = chat([message], temperature=0.88, top_p=0.7, with_search_enhance=True) + print(response) assert isinstance(response, AIMessage) assert isinstance(response.content, str) def test_extra_kwargs() -> None: - chat = ChatBaichuan(temperature=0.88, top_p=0.7) + chat = ChatBaichuan(temperature=0.88, top_p=0.7, with_search_enhance=True) assert chat.temperature == 0.88 assert chat.top_p == 0.7 + assert chat.with_search_enhance is True diff --git a/libs/community/tests/unit_tests/chat_models/test_baichuan.py b/libs/community/tests/unit_tests/chat_models/test_baichuan.py index 6a4b4d2009cf4..f5664c88ffe56 100644 --- a/libs/community/tests/unit_tests/chat_models/test_baichuan.py +++ b/libs/community/tests/unit_tests/chat_models/test_baichuan.py @@ -18,7 +18,6 @@ _convert_delta_to_message_chunk, _convert_dict_to_message, _convert_message_to_dict, - _signature, ) @@ -85,62 +84,33 @@ def test__convert_delta_to_message_human() -> None: assert result == expected_output -def test__signature() -> None: - secret_key = SecretStr("YOUR_SECRET_KEY") - - result = _signature( - secret_key=secret_key, - payload={ - "model": "Baichuan2-53B", - "messages": [{"role": "user", "content": "Hi"}], - }, - timestamp=1697734335, - ) - - # The signature was generated by the demo provided by Baichuan. - # https://platform.baichuan-ai.com/docs/api#4 - expected_output = "24a50b2db1648e25a244c67c5ab57d3f" - assert result == expected_output - - def test_baichuan_key_masked_when_passed_from_env( monkeypatch: MonkeyPatch, capsys: CaptureFixture ) -> None: """Test initialization with an API key provided via an env variable""" monkeypatch.setenv("BAICHUAN_API_KEY", "test-api-key") - monkeypatch.setenv("BAICHUAN_SECRET_KEY", "test-secret-key") chat = ChatBaichuan() print(chat.baichuan_api_key, end="") captured = capsys.readouterr() assert captured.out == "**********" - print(chat.baichuan_secret_key, end="") - captured = capsys.readouterr() - assert captured.out == "**********" - def test_baichuan_key_masked_when_passed_via_constructor( capsys: CaptureFixture, ) -> None: """Test initialization with an API key provided via the initializer""" - chat = ChatBaichuan( - baichuan_api_key="test-api-key", baichuan_secret_key="test-secret-key" - ) + chat = ChatBaichuan(baichuan_api_key="test-api-key") print(chat.baichuan_api_key, end="") captured = capsys.readouterr() assert captured.out == "**********" - print(chat.baichuan_secret_key, end="") - captured = capsys.readouterr() - - assert captured.out == "**********" - def test_uses_actual_secret_value_from_secret_str() -> None: """Test that actual secret is retrieved using `.get_secret_value()`.""" chat = ChatBaichuan( - baichuan_api_key="test-api-key", baichuan_secret_key="test-secret-key" + baichuan_api_key="test-api-key", + baichuan_secret_key="test-secret-key", # For backward compatibility ) assert cast(SecretStr, chat.baichuan_api_key).get_secret_value() == "test-api-key" assert ( From 9ce177580a9346a54c2f47498f12c338ec46f7d3 Mon Sep 17 00:00:00 2001 From: Davide Menini <48685774+dmenini@users.noreply.github.com> Date: Wed, 24 Jan 2024 02:05:24 +0100 Subject: [PATCH 194/215] community: normalize bedrock embeddings (#15103) In this PR I added a post-processing function to normalize the embeddings. This happens only if the new `normalize` flag is `True`. --------- Co-authored-by: taamedag <Davide.Menini@swisscom.com> --- .../langchain_community/embeddings/bedrock.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/embeddings/bedrock.py b/libs/community/langchain_community/embeddings/bedrock.py index 529809fb91163..7ab94df4dcb92 100644 --- a/libs/community/langchain_community/embeddings/bedrock.py +++ b/libs/community/langchain_community/embeddings/bedrock.py @@ -3,6 +3,7 @@ import os from typing import Any, Dict, List, Optional +import numpy as np from langchain_core.embeddings import Embeddings from langchain_core.pydantic_v1 import BaseModel, Extra, root_validator from langchain_core.runnables.config import run_in_executor @@ -64,6 +65,9 @@ class BedrockEmbeddings(BaseModel, Embeddings): endpoint_url: Optional[str] = None """Needed if you don't want to default to us-east-1 endpoint""" + normalize: bool = False + """Whether the embeddings should be normalized to unit vectors""" + class Config: """Configuration for this pydantic object.""" @@ -145,6 +149,12 @@ def _embedding_func(self, text: str) -> List[float]: except Exception as e: raise ValueError(f"Error raised by inference endpoint: {e}") + def _normalize_vector(self, embeddings: List[float]) -> List[float]: + """Normalize the embedding to a unit vector.""" + emb = np.array(embeddings) + norm_emb = emb / np.linalg.norm(emb) + return norm_emb.tolist() + def embed_documents(self, texts: List[str]) -> List[List[float]]: """Compute doc embeddings using a Bedrock model. @@ -157,7 +167,12 @@ def embed_documents(self, texts: List[str]) -> List[List[float]]: results = [] for text in texts: response = self._embedding_func(text) + + if self.normalize: + response = self._normalize_vector(response) + results.append(response) + return results def embed_query(self, text: str) -> List[float]: @@ -169,7 +184,12 @@ def embed_query(self, text: str) -> List[float]: Returns: Embeddings for the text. """ - return self._embedding_func(text) + embedding = self._embedding_func(text) + + if self.normalize: + return self._normalize_vector(embedding) + + return embedding async def aembed_query(self, text: str) -> List[float]: """Asynchronous compute query embeddings using a Bedrock model. From 92e6a641fdba02c4f5b1102fa5addfc83d29ace8 Mon Sep 17 00:00:00 2001 From: Facundo Santiago <santiagof@outlook.com> Date: Tue, 23 Jan 2024 22:08:51 -0300 Subject: [PATCH 195/215] feat: adding paygo api support for Azure ML / Azure AI Studio (#14560) - **Description:** Introducing support for LLMs and Chat models running in Azure AI studio and Azure ML using the new deployment mode pay-as-you-go (model as a service). - **Issue:** NA - **Dependencies:** None. - **Tag maintainer:** @prakharg-msft @gdyre - **Twitter handle:** @santiagofacundo Examples added: * [docs/docs/integrations/llms/azure_ml.ipynb](https://github.com/santiagxf/langchain/blob/santiagxf/azureml-endpoints-paygo-community/docs/docs/integrations/chat/azureml_endpoint.ipynb) * [docs/docs/integrations/chat/azureml_chat_endpoint.ipynb](https://github.com/santiagxf/langchain/blob/santiagxf/azureml-endpoints-paygo-community/docs/docs/integrations/chat/azureml_chat_endpoint.ipynb) --------- Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> --- .../chat/azureml_chat_endpoint.ipynb | 120 ++++++- docs/docs/integrations/llms/azure_ml.ipynb | 161 ++++++--- .../chat_models/azureml_endpoint.py | 166 +++++---- .../llms/azureml_endpoint.py | 332 ++++++++++++++---- .../chat_models/test_azureml_endpoint.py | 20 +- .../llms/test_azureml_endpoint.py | 39 +- 6 files changed, 631 insertions(+), 207 deletions(-) diff --git a/docs/docs/integrations/chat/azureml_chat_endpoint.ipynb b/docs/docs/integrations/chat/azureml_chat_endpoint.ipynb index 61d3cf14cda5b..6fe4c869f4f94 100644 --- a/docs/docs/integrations/chat/azureml_chat_endpoint.ipynb +++ b/docs/docs/integrations/chat/azureml_chat_endpoint.ipynb @@ -15,9 +15,9 @@ "source": [ "# AzureMLChatOnlineEndpoint\n", "\n", - ">[Azure Machine Learning](https://azure.microsoft.com/en-us/products/machine-learning/) is a platform used to build, train, and deploy machine learning models. Users can explore the types of models to deploy in the Model Catalog, which provides Azure Foundation Models and OpenAI Models. `Azure Foundation Models` include various open-source models and popular Hugging Face models. Users can also import models of their liking into AzureML.\n", + ">[Azure Machine Learning](https://azure.microsoft.com/en-us/products/machine-learning/) is a platform used to build, train, and deploy machine learning models. Users can explore the types of models to deploy in the Model Catalog, which provides foundational and general purpose models from different providers.\n", ">\n", - ">[Azure Machine Learning Online Endpoints](https://learn.microsoft.com/en-us/azure/machine-learning/concept-endpoints). After you train machine learning models or pipelines, you need to deploy them to production so that others can use them for inference. Inference is the process of applying new input data to the machine learning model or pipeline to generate outputs. While these outputs are typically referred to as \"predictions,\" inferencing can be used to generate outputs for other machine learning tasks, such as classification and clustering. In `Azure Machine Learning`, you perform inferencing by using endpoints and deployments. `Endpoints` and `Deployments` allow you to decouple the interface of your production workload from the implementation that serves it.\n", + ">In general, you need to deploy models in order to consume its predictions (inference). In `Azure Machine Learning`, [Online Endpoints](https://learn.microsoft.com/en-us/azure/machine-learning/concept-endpoints) are used to deploy these models with a real-time serving. They are based on the ideas of `Endpoints` and `Deployments` which allow you to decouple the interface of your production workload from the implementation that serves it.\n", "\n", "This notebook goes over how to use a chat model hosted on an `Azure Machine Learning Endpoint`." ] @@ -37,10 +37,11 @@ "source": [ "## Set up\n", "\n", - "To use the wrapper, you must [deploy a model on AzureML](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-use-foundation-models?view=azureml-api-2#deploying-foundation-models-to-endpoints-for-inferencing) and obtain the following parameters:\n", + "You must [deploy a model on Azure ML](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-use-foundation-models?view=azureml-api-2#deploying-foundation-models-to-endpoints-for-inferencing) or [to Azure AI studio](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/deploy-models-open) and obtain the following parameters:\n", "\n", - "* `endpoint_api_key`: The API key provided by the endpoint\n", - "* `endpoint_url`: The REST endpoint url provided by the endpoint" + "* `endpoint_url`: The REST endpoint url provided by the endpoint.\n", + "* `endpoint_api_type`: Use `endpoint_type='realtime'` when deploying models to **Realtime endpoints** (hosted managed infrastructure). Use `endpoint_type='serverless'` when deploying models using the **Pay-as-you-go** offering (model as a service).\n", + "* `endpoint_api_key`: The API key provided by the endpoint" ] }, { @@ -51,7 +52,40 @@ "\n", "The `content_formatter` parameter is a handler class for transforming the request and response of an AzureML endpoint to match with required schema. Since there are a wide range of models in the model catalog, each of which may process data differently from one another, a `ContentFormatterBase` class is provided to allow users to transform data to their liking. The following content formatters are provided:\n", "\n", - "* `LLamaContentFormatter`: Formats request and response data for LLaMa2-chat" + "* `LLamaChatContentFormatter`: Formats request and response data for LLaMa2-chat\n", + "\n", + "*Note: `langchain.chat_models.azureml_endpoint.LLamaContentFormatter` is being deprecated and replaced with `langchain.chat_models.azureml_endpoint.LLamaChatContentFormatter`.*\n", + "\n", + "You can implement custom content formatters specific for your model deriving from the class `langchain_community.llms.azureml_endpoint.ContentFormatterBase`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples\n", + "\n", + "The following section cotain examples about how to use this class:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.schema import HumanMessage\n", + "from langchain_community.chat_models.azureml_endpoint import (\n", + " AzureMLEndpointApiType,\n", + " LlamaChatContentFormatter,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Chat completions with real-time endpoints" ] }, { @@ -76,11 +110,79 @@ "\n", "chat = AzureMLChatOnlineEndpoint(\n", " endpoint_url=\"https://<your-endpoint>.<your_region>.inference.ml.azure.com/score\",\n", + " endpoint_api_type=AzureMLEndpointApiType.realtime,\n", " endpoint_api_key=\"my-api-key\",\n", - " content_formatter=LlamaContentFormatter,\n", + " content_formatter=LlamaChatContentFormatter(),\n", ")\n", - "response = chat(\n", - " messages=[HumanMessage(content=\"Will the Collatz conjecture ever be solved?\")]\n", + "response = chat.invoke(\n", + " [HumanMessage(content=\"Will the Collatz conjecture ever be solved?\")]\n", + ")\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Chat completions with pay-as-you-go deployments (model as a service)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "chat = AzureMLChatOnlineEndpoint(\n", + " endpoint_url=\"https://<your-endpoint>.<your_region>.inference.ml.azure.com/v1/chat/completions\",\n", + " endpoint_api_type=AzureMLEndpointApiType.serverless,\n", + " endpoint_api_key=\"my-api-key\",\n", + " content_formatter=LlamaChatContentFormatter,\n", + ")\n", + "response = chat.invoke(\n", + " [HumanMessage(content=\"Will the Collatz conjecture ever be solved?\")]\n", + ")\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you need to pass additional parameters to the model, use `model_kwards` argument:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "chat = AzureMLChatOnlineEndpoint(\n", + " endpoint_url=\"https://<your-endpoint>.<your_region>.inference.ml.azure.com/v1/chat/completions\",\n", + " endpoint_api_type=AzureMLEndpointApiType.serverless,\n", + " endpoint_api_key=\"my-api-key\",\n", + " content_formatter=LlamaChatContentFormatter,\n", + " model_kwargs={\"temperature\": 0.8},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parameters can also be passed during invocation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "response = chat.invoke(\n", + " [HumanMessage(content=\"Will the Collatz conjecture ever be solved?\")],\n", + " max_tokens=512,\n", ")\n", "response" ] diff --git a/docs/docs/integrations/llms/azure_ml.ipynb b/docs/docs/integrations/llms/azure_ml.ipynb index 63aa8a2cb0633..9d066bddb3cc9 100644 --- a/docs/docs/integrations/llms/azure_ml.ipynb +++ b/docs/docs/integrations/llms/azure_ml.ipynb @@ -6,9 +6,9 @@ "source": [ "# Azure ML\n", "\n", - "[Azure ML](https://azure.microsoft.com/en-us/products/machine-learning/) is a platform used to build, train, and deploy machine learning models. Users can explore the types of models to deploy in the Model Catalog, which provides Azure Foundation Models and OpenAI Models. Azure Foundation Models include various open-source models and popular Hugging Face models. Users can also import models of their liking into AzureML.\n", + "[Azure ML](https://azure.microsoft.com/en-us/products/machine-learning/) is a platform used to build, train, and deploy machine learning models. Users can explore the types of models to deploy in the Model Catalog, which provides foundational and general purpose models from different providers.\n", "\n", - "This notebook goes over how to use an LLM hosted on an `AzureML online endpoint`" + "This notebook goes over how to use an LLM hosted on an `Azure ML Online Endpoint`." ] }, { @@ -26,11 +26,12 @@ "source": [ "## Set up\n", "\n", - "To use the wrapper, you must [deploy a model on AzureML](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-use-foundation-models?view=azureml-api-2#deploying-foundation-models-to-endpoints-for-inferencing) and obtain the following parameters:\n", + "You must [deploy a model on Azure ML](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-use-foundation-models?view=azureml-api-2#deploying-foundation-models-to-endpoints-for-inferencing) or [to Azure AI studio](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/deploy-models-open) and obtain the following parameters:\n", "\n", - "* `endpoint_api_key`: Required - The API key provided by the endpoint\n", - "* `endpoint_url`: Required - The REST endpoint url provided by the endpoint\n", - "* `deployment_name`: Not required - The deployment name of the model using the endpoint" + "* `endpoint_url`: The REST endpoint url provided by the endpoint.\n", + "* `endpoint_api_type`: Use `endpoint_type='realtime'` when deploying models to **Realtime endpoints** (hosted managed infrastructure). Use `endpoint_type='serverless'` when deploying models using the **Pay-as-you-go** offering (model as a service).\n", + "* `endpoint_api_key`: The API key provided by the endpoint.\n", + "* `deployment_name`: (Optional) The deployment name of the model using the endpoint." ] }, { @@ -46,31 +47,107 @@ "* `HFContentFormatter`: Formats request and response data for text-generation Hugging Face models\n", "* `LLamaContentFormatter`: Formats request and response data for LLaMa2\n", "\n", - "*Note: `OSSContentFormatter` is being deprecated and replaced with `GPT2ContentFormatter`. The logic is the same but `GPT2ContentFormatter` is a more suitable name. You can still continue to use `OSSContentFormatter` as the changes are backwards compatible.*\n", + "*Note: `OSSContentFormatter` is being deprecated and replaced with `GPT2ContentFormatter`. The logic is the same but `GPT2ContentFormatter` is a more suitable name. You can still continue to use `OSSContentFormatter` as the changes are backwards compatible.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: LlaMa 2 completions with real-time endpoints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.schema import HumanMessage\n", + "from langchain_community.llms.azureml_endpoint import (\n", + " AzureMLEndpointApiType,\n", + " LlamaContentFormatter,\n", + ")\n", "\n", - "Below is an example using a summarization model from Hugging Face." + "llm = AzureMLOnlineEndpoint(\n", + " endpoint_url=\"https://<your-endpoint>.<your_region>.inference.ml.azure.com/score\",\n", + " endpoint_api_type=AzureMLEndpointApiType.realtime,\n", + " endpoint_api_key=\"my-api-key\",\n", + " content_formatter=LlamaContentFormatter(),\n", + " model_kwargs={\"temperature\": 0.8, \"max_new_tokens\": 400},\n", + ")\n", + "response = llm.invoke(\"Write me a song about sparkling water:\")\n", + "response" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Custom Content Formatter" + "Model parameters can also be indicated during invocation:" ] }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "HaSeul won her first music show trophy with \"So What\" on Mnet's M Countdown. Loona released their second EP titled [#] (read as hash] on February 5, 2020. HaSeul did not take part in the promotion of the album because of mental health issues. On October 19, 2020, they released their third EP called [12:00]. It was their first album to enter the Billboard 200, debuting at number 112. On June 2, 2021, the group released their fourth EP called Yummy-Yummy. On August 27, it was announced that they are making their Japanese debut on September 15 under Universal Music Japan sublabel EMI Records.\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "response = llm.invoke(\"Write me a song about sparkling water:\", temperature=0.5)\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Chat completions with pay-as-you-go deployments (model as a service)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.schema import HumanMessage\n", + "from langchain_community.llms.azureml_endpoint import (\n", + " AzureMLEndpointApiType,\n", + " LlamaContentFormatter,\n", + ")\n", + "\n", + "llm = AzureMLOnlineEndpoint(\n", + " endpoint_url=\"https://<your-endpoint>.<your_region>.inference.ml.azure.com/v1/completions\",\n", + " endpoint_api_type=AzureMLEndpointApiType.serverless,\n", + " endpoint_api_key=\"my-api-key\",\n", + " content_formatter=LlamaContentFormatter(),\n", + " model_kwargs={\"temperature\": 0.8, \"max_new_tokens\": 400},\n", + ")\n", + "response = llm.invoke(\"Write me a song about sparkling water:\")\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Custom content formatter\n", + "\n", + "Below is an example using a summarization model from Hugging Face." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import json\n", "import os\n", @@ -104,6 +181,7 @@ "content_formatter = CustomFormatter()\n", "\n", "llm = AzureMLOnlineEndpoint(\n", + " endpoint_api_type=\"realtime\",\n", " endpoint_api_key=os.getenv(\"BART_ENDPOINT_API_KEY\"),\n", " endpoint_url=os.getenv(\"BART_ENDPOINT_URL\"),\n", " model_kwargs={\"temperature\": 0.8, \"max_new_tokens\": 400},\n", @@ -132,7 +210,7 @@ "that Loona will release the double A-side single, \"Hula Hoop / Star Seed\" on September 15, with a physical CD release on October \n", "20.[53] In December, Chuu filed an injunction to suspend her exclusive contract with Blockberry Creative.[54][55]\n", "\"\"\"\n", - "summarized_text = llm(large_text)\n", + "summarized_text = llm.invoke(large_text)\n", "print(summarized_text)" ] }, @@ -140,22 +218,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Dolly with LLMChain" + "### Example: Dolly with LLMChain" ] }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Many people are willing to talk about themselves; it's others who seem to be stuck up. Try to understand others where they're coming from. Like minded people can build a tribe together.\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from langchain.chains import LLMChain\n", "from langchain.prompts import PromptTemplate\n", @@ -177,31 +247,22 @@ ")\n", "\n", "chain = LLMChain(llm=llm, prompt=prompt)\n", - "print(chain.run({\"word_count\": 100, \"topic\": \"how to make friends\"}))" + "print(chain.invoke({\"word_count\": 100, \"topic\": \"how to make friends\"}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Serializing an LLM\n", + "## Serializing an LLM\n", "You can also save and load LLM configurations" ] }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1mAzureMLOnlineEndpoint\u001b[0m\n", - "Params: {'deployment_name': 'databricks-dolly-v2-12b-4', 'model_kwargs': {'temperature': 0.2, 'max_tokens': 150, 'top_p': 0.8, 'frequency_penalty': 0.32, 'presence_penalty': 0.072}}\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from langchain_community.llms.loading import load_llm\n", "\n", @@ -224,9 +285,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "langchain", "language": "python", - "name": "python3" + "name": "langchain" }, "language_info": { "codemirror_mode": { @@ -238,7 +299,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/libs/community/langchain_community/chat_models/azureml_endpoint.py b/libs/community/langchain_community/chat_models/azureml_endpoint.py index 111f0502b1fc1..58192d6cdce70 100644 --- a/libs/community/langchain_community/chat_models/azureml_endpoint.py +++ b/libs/community/langchain_community/chat_models/azureml_endpoint.py @@ -1,8 +1,8 @@ import json from typing import Any, Dict, List, Optional, cast -from langchain_core.callbacks import CallbackManagerForLLMRun -from langchain_core.language_models.chat_models import SimpleChatModel +from langchain_core.callbacks.manager import CallbackManagerForLLMRun +from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.messages import ( AIMessage, BaseMessage, @@ -10,16 +10,24 @@ HumanMessage, SystemMessage, ) -from langchain_core.pydantic_v1 import SecretStr, validator -from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env +from langchain_core.outputs import ChatGeneration, ChatResult from langchain_community.llms.azureml_endpoint import ( - AzureMLEndpointClient, + AzureMLBaseEndpoint, + AzureMLEndpointApiType, ContentFormatterBase, ) class LlamaContentFormatter(ContentFormatterBase): + def __init__(self): + raise TypeError( + "`LlamaContentFormatter` is deprecated for chat models. Use " + "`LlamaChatContentFormatter` instead." + ) + + +class LlamaChatContentFormatter(ContentFormatterBase): """Content formatter for `LLaMA`.""" SUPPORTED_ROLES: List[str] = ["user", "assistant", "system"] @@ -45,7 +53,7 @@ def _convert_message_to_dict(message: BaseMessage) -> Dict: } elif ( isinstance(message, ChatMessage) - and message.role in LlamaContentFormatter.SUPPORTED_ROLES + and message.role in LlamaChatContentFormatter.SUPPORTED_ROLES ): return { "role": message.role, @@ -53,79 +61,96 @@ def _convert_message_to_dict(message: BaseMessage) -> Dict: } else: supported = ",".join( - [role for role in LlamaContentFormatter.SUPPORTED_ROLES] + [role for role in LlamaChatContentFormatter.SUPPORTED_ROLES] ) raise ValueError( f"""Received unsupported role. Supported roles for the LLaMa Foundation Model: {supported}""" ) - def _format_request_payload( - self, messages: List[BaseMessage], model_kwargs: Dict - ) -> bytes: + @property + def supported_api_types(self) -> List[AzureMLEndpointApiType]: + return [AzureMLEndpointApiType.realtime, AzureMLEndpointApiType.serverless] + + def format_request_payload( + self, + messages: List[BaseMessage], + model_kwargs: Dict, + api_type: AzureMLEndpointApiType, + ) -> str: + """Formats the request according to the chosen api""" chat_messages = [ - LlamaContentFormatter._convert_message_to_dict(message) + LlamaChatContentFormatter._convert_message_to_dict(message) for message in messages ] - prompt = json.dumps( - {"input_data": {"input_string": chat_messages, "parameters": model_kwargs}} - ) - return self.format_request_payload(prompt=prompt, model_kwargs=model_kwargs) - - def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: - """Formats the request according to the chosen api""" - return str.encode(prompt) + if api_type == AzureMLEndpointApiType.realtime: + request_payload = json.dumps( + { + "input_data": { + "input_string": chat_messages, + "parameters": model_kwargs, + } + } + ) + elif api_type == AzureMLEndpointApiType.serverless: + request_payload = json.dumps({"messages": chat_messages, **model_kwargs}) + else: + raise ValueError( + f"`api_type` {api_type} is not supported by this formatter" + ) + return str.encode(request_payload) - def format_response_payload(self, output: bytes) -> str: + def format_response_payload( + self, output: bytes, api_type: AzureMLEndpointApiType + ) -> ChatGeneration: """Formats response""" - return json.loads(output)["output"] + if api_type == AzureMLEndpointApiType.realtime: + try: + choice = json.loads(output)["output"] + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return ChatGeneration( + message=BaseMessage( + content=choice.strip(), + type="assistant", + ), + generation_info=None, + ) + if api_type == AzureMLEndpointApiType.serverless: + try: + choice = json.loads(output)["choices"][0] + if not isinstance(choice, dict): + raise TypeError( + "Endpoint response is not well formed for a chat " + "model. Expected `dict` but `{type(choice)}` was received." + ) + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return ChatGeneration( + message=BaseMessage( + content=choice["message"]["content"].strip(), + type=choice["message"]["role"], + ), + generation_info=dict( + finish_reason=choice.get("finish_reason"), + logprobs=choice.get("logprobs"), + ), + ) + raise ValueError(f"`api_type` {api_type} is not supported by this formatter") -class AzureMLChatOnlineEndpoint(SimpleChatModel): - """`AzureML` Chat models API. +class AzureMLChatOnlineEndpoint(BaseChatModel, AzureMLBaseEndpoint): + """Azure ML Online Endpoint chat models. Example: .. code-block:: python - - azure_chat = AzureMLChatOnlineEndpoint( + azure_llm = AzureMLOnlineEndpoint( endpoint_url="https://<your-endpoint>.<your_region>.inference.ml.azure.com/score", + endpoint_api_type=AzureMLApiType.realtime, endpoint_api_key="my-api-key", - content_formatter=content_formatter, + content_formatter=chat_content_formatter, ) - """ - - endpoint_url: str = "" - """URL of pre-existing Endpoint. Should be passed to constructor or specified as - env var `AZUREML_ENDPOINT_URL`.""" - - endpoint_api_key: SecretStr = convert_to_secret_str("") - """Authentication Key for Endpoint. Should be passed to constructor or specified as - env var `AZUREML_ENDPOINT_API_KEY`.""" - - http_client: Any = None #: :meta private: - - content_formatter: Any = None - """The content formatter that provides an input and output - transform function to handle formats between the LLM and - the endpoint""" - - model_kwargs: Optional[dict] = None - """Keyword arguments to pass to the model.""" - - @validator("http_client", always=True, allow_reuse=True) - @classmethod - def validate_client(cls, field_value: Any, values: Dict) -> AzureMLEndpointClient: - """Validate that api key and python package exist in environment.""" - values["endpoint_api_key"] = convert_to_secret_str( - get_from_dict_or_env(values, "endpoint_api_key", "AZUREML_ENDPOINT_API_KEY") - ) - endpoint_url = get_from_dict_or_env( - values, "endpoint_url", "AZUREML_ENDPOINT_URL" - ) - http_client = AzureMLEndpointClient( - endpoint_url, values["endpoint_api_key"].get_secret_value() - ) - return http_client + """ # noqa: E501 @property def _identifying_params(self) -> Dict[str, Any]: @@ -140,13 +165,13 @@ def _llm_type(self) -> str: """Return type of llm.""" return "azureml_chat_endpoint" - def _call( + def _generate( self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, - ) -> str: + ) -> ChatResult: """Call out to an AzureML Managed Online endpoint. Args: messages: The messages in the conversation with the chat model. @@ -158,12 +183,17 @@ def _call( response = azureml_model("Tell me a joke.") """ _model_kwargs = self.model_kwargs or {} + _model_kwargs.update(kwargs) + if stop: + _model_kwargs["stop"] = stop - request_payload = self.content_formatter._format_request_payload( - messages, _model_kwargs + request_payload = self.content_formatter.format_request_payload( + messages, _model_kwargs, self.endpoint_api_type + ) + response_payload = self.http_client.call( + body=request_payload, run_manager=run_manager ) - response_payload = self.http_client.call(request_payload, **kwargs) - generated_text = self.content_formatter.format_response_payload( - response_payload + generations = self.content_formatter.format_response_payload( + response_payload, self.endpoint_api_type ) - return generated_text + return ChatResult(generations=[generations]) diff --git a/libs/community/langchain_community/llms/azureml_endpoint.py b/libs/community/langchain_community/llms/azureml_endpoint.py index c9e73df6c6345..3480a801f96f6 100644 --- a/libs/community/langchain_community/llms/azureml_endpoint.py +++ b/libs/community/langchain_community/llms/azureml_endpoint.py @@ -2,12 +2,14 @@ import urllib.request import warnings from abc import abstractmethod +from enum import Enum from typing import Any, Dict, List, Mapping, Optional -from langchain_core.callbacks import CallbackManagerForLLMRun -from langchain_core.language_models.llms import LLM -from langchain_core.pydantic_v1 import BaseModel, validator -from langchain_core.utils import get_from_dict_or_env +from langchain_core.callbacks.manager import CallbackManagerForLLMRun +from langchain_core.language_models.llms import BaseLLM +from langchain_core.outputs import Generation, LLMResult +from langchain_core.pydantic_v1 import BaseModel, SecretStr, root_validator, validator +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env class AzureMLEndpointClient(object): @@ -26,7 +28,12 @@ def __init__( self.endpoint_api_key = endpoint_api_key self.deployment_name = deployment_name - def call(self, body: bytes, **kwargs: Any) -> bytes: + def call( + self, + body: bytes, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> bytes: """call.""" # The azureml-model-deployment header will force the request to go to a @@ -45,6 +52,16 @@ def call(self, body: bytes, **kwargs: Any) -> bytes: return result +class AzureMLEndpointApiType(str, Enum): + """Azure ML endpoints API types. Use `realtime` for models deployed in hosted + infrastructure, or `serverless` for models deployed as a service with a + pay-as-you-go billing or PTU. + """ + + realtime = "realtime" + serverless = "serverless" + + class ContentFormatterBase: """Transform request and response of AzureML endpoint to match with required schema. @@ -61,7 +78,8 @@ class ContentFormatter(ContentFormatterBase): def format_request_payload( self, prompt: str, - model_kwargs: Dict + model_kwargs: Dict, + api_type: AzureMLEndpointApiType, ) -> bytes: input_str = json.dumps( { @@ -71,7 +89,9 @@ def format_request_payload( ) return str.encode(input_str) - def format_response_payload(self, output: str) -> str: + def format_response_payload( + self, output: str, api_type: AzureMLEndpointApiType + ) -> str: response_json = json.loads(output) return response_json[0]["0"] """ @@ -81,6 +101,12 @@ def format_response_payload(self, output: str) -> str: accepts: Optional[str] = "application/json" """The MIME type of the response data returned from the endpoint""" + format_error_msg: Optional[str] = ( + "Error while formatting response payload for chat model of type " + " `{api_type}`. Are you using the right formatter for the deployed " + " model and endpoint type?" + ) + @staticmethod def escape_special_characters(prompt: str) -> str: """Escapes any special characters in `prompt`""" @@ -100,15 +126,32 @@ def escape_special_characters(prompt: str) -> str: return prompt + @property + def supported_api_types(self) -> List[AzureMLEndpointApiType]: + """Supported APIs for the given formatter. Azure ML supports + deploying models using different hosting methods. Each method may have + a different API structure.""" + + return [AzureMLEndpointApiType.realtime] + @abstractmethod - def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: + def format_request_payload( + self, + prompt: str, + model_kwargs: Dict, + api_type: AzureMLEndpointApiType = AzureMLEndpointApiType.realtime, + ) -> bytes: """Formats the request body according to the input schema of the model. Returns bytes or seekable file like object in the format specified in the content_type request header. """ @abstractmethod - def format_response_payload(self, output: bytes) -> str: + def format_response_payload( + self, + output: bytes, + api_type: AzureMLEndpointApiType = AzureMLEndpointApiType.realtime, + ) -> Generation: """Formats the response body according to the output schema of the model. Returns the data type that is received from the response. @@ -118,15 +161,27 @@ def format_response_payload(self, output: bytes) -> str: class GPT2ContentFormatter(ContentFormatterBase): """Content handler for GPT2""" - def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: + @property + def supported_api_types(self) -> List[AzureMLEndpointApiType]: + return [AzureMLEndpointApiType.realtime] + + def format_request_payload( + self, prompt: str, model_kwargs: Dict, api_type: AzureMLEndpointApiType + ) -> bytes: prompt = ContentFormatterBase.escape_special_characters(prompt) request_payload = json.dumps( {"inputs": {"input_string": [f'"{prompt}"']}, "parameters": model_kwargs} ) return str.encode(request_payload) - def format_response_payload(self, output: bytes) -> str: - return json.loads(output)[0]["0"] + def format_response_payload( + self, output: bytes, api_type: AzureMLEndpointApiType + ) -> Generation: + try: + choice = json.loads(output)[0]["0"] + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return Generation(text=choice) class OSSContentFormatter(GPT2ContentFormatter): @@ -148,21 +203,39 @@ def __init__(self) -> None: class HFContentFormatter(ContentFormatterBase): """Content handler for LLMs from the HuggingFace catalog.""" - def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: + @property + def supported_api_types(self) -> List[AzureMLEndpointApiType]: + return [AzureMLEndpointApiType.realtime] + + def format_request_payload( + self, prompt: str, model_kwargs: Dict, api_type: AzureMLEndpointApiType + ) -> bytes: ContentFormatterBase.escape_special_characters(prompt) request_payload = json.dumps( {"inputs": [f'"{prompt}"'], "parameters": model_kwargs} ) return str.encode(request_payload) - def format_response_payload(self, output: bytes) -> str: - return json.loads(output)[0]["generated_text"] + def format_response_payload( + self, output: bytes, api_type: AzureMLEndpointApiType + ) -> Generation: + try: + choice = json.loads(output)[0]["0"]["generated_text"] + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return Generation(text=choice) class DollyContentFormatter(ContentFormatterBase): """Content handler for the Dolly-v2-12b model""" - def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: + @property + def supported_api_types(self) -> List[AzureMLEndpointApiType]: + return [AzureMLEndpointApiType.realtime] + + def format_request_payload( + self, prompt: str, model_kwargs: Dict, api_type: AzureMLEndpointApiType + ) -> bytes: prompt = ContentFormatterBase.escape_special_characters(prompt) request_payload = json.dumps( { @@ -172,49 +245,88 @@ def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: ) return str.encode(request_payload) - def format_response_payload(self, output: bytes) -> str: - return json.loads(output)[0] + def format_response_payload( + self, output: bytes, api_type: AzureMLEndpointApiType + ) -> Generation: + try: + choice = json.loads(output)[0] + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return Generation(text=choice) class LlamaContentFormatter(ContentFormatterBase): """Content formatter for LLaMa""" - def format_request_payload(self, prompt: str, model_kwargs: Dict) -> bytes: + @property + def supported_api_types(self) -> List[AzureMLEndpointApiType]: + return [AzureMLEndpointApiType.realtime, AzureMLEndpointApiType.serverless] + + def format_request_payload( + self, prompt: str, model_kwargs: Dict, api_type: AzureMLEndpointApiType + ) -> bytes: """Formats the request according to the chosen api""" prompt = ContentFormatterBase.escape_special_characters(prompt) - request_payload = json.dumps( - { - "input_data": { - "input_string": [f'"{prompt}"'], - "parameters": model_kwargs, + if api_type == AzureMLEndpointApiType.realtime: + request_payload = json.dumps( + { + "input_data": { + "input_string": [f'"{prompt}"'], + "parameters": model_kwargs, + } } - } - ) + ) + elif api_type == AzureMLEndpointApiType.serverless: + request_payload = json.dumps({"prompt": prompt, **model_kwargs}) + else: + raise ValueError( + f"`api_type` {api_type} is not supported by this formatter" + ) return str.encode(request_payload) - def format_response_payload(self, output: bytes) -> str: + def format_response_payload( + self, output: bytes, api_type: AzureMLEndpointApiType + ) -> Generation: """Formats response""" - return json.loads(output)[0]["0"] - + if api_type == AzureMLEndpointApiType.realtime: + try: + choice = json.loads(output)[0]["0"] + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return Generation(text=choice) + if api_type == AzureMLEndpointApiType.serverless: + try: + choice = json.loads(output)["choices"][0] + if not isinstance(choice, dict): + raise TypeError( + "Endpoint response is not well formed for a chat " + "model. Expected `dict` but `{type(choice)}` was " + "received." + ) + except (KeyError, IndexError, TypeError) as e: + raise ValueError(self.format_error_msg.format(api_type=api_type)) from e + return Generation( + text=choice["text"].strip(), + generation_info=dict( + finish_reason=choice.get("finish_reason"), + logprobs=choice.get("logprobs"), + ), + ) + raise ValueError(f"`api_type` {api_type} is not supported by this formatter") -class AzureMLOnlineEndpoint(LLM, BaseModel): - """Azure ML Online Endpoint models. - Example: - .. code-block:: python - - azure_llm = AzureMLOnlineEndpoint( - endpoint_url="https://<your-endpoint>.<your_region>.inference.ml.azure.com/score", - endpoint_api_key="my-api-key", - content_formatter=content_formatter, - ) - """ # noqa: E501 +class AzureMLBaseEndpoint(BaseModel): + """Azure ML Online Endpoint models.""" endpoint_url: str = "" """URL of pre-existing Endpoint. Should be passed to constructor or specified as env var `AZUREML_ENDPOINT_URL`.""" - endpoint_api_key: str = "" + endpoint_api_type: AzureMLEndpointApiType = AzureMLEndpointApiType.realtime + """Type of the endpoint being consumed. Possible values are `serverless` for + pay-as-you-go and `realtime` for real-time endpoints. """ + + endpoint_api_key: SecretStr = convert_to_secret_str("") """Authentication Key for Endpoint. Should be passed to constructor or specified as env var `AZUREML_ENDPOINT_API_KEY`.""" @@ -232,22 +344,106 @@ class AzureMLOnlineEndpoint(LLM, BaseModel): model_kwargs: Optional[dict] = None """Keyword arguments to pass to the model.""" - @validator("http_client", always=True, allow_reuse=True) - @classmethod - def validate_client(cls, field_value: Any, values: Dict) -> AzureMLEndpointClient: - """Validate that api key and python package exists in environment.""" - endpoint_key = get_from_dict_or_env( - values, "endpoint_api_key", "AZUREML_ENDPOINT_API_KEY" + @root_validator(pre=True) + def validate_environ(cls, values: Dict) -> Dict: + values["endpoint_api_key"] = convert_to_secret_str( + get_from_dict_or_env(values, "endpoint_api_key", "AZUREML_ENDPOINT_API_KEY") ) - endpoint_url = get_from_dict_or_env( + values["endpoint_url"] = get_from_dict_or_env( values, "endpoint_url", "AZUREML_ENDPOINT_URL" ) - deployment_name = get_from_dict_or_env( + values["deployment_name"] = get_from_dict_or_env( values, "deployment_name", "AZUREML_DEPLOYMENT_NAME", "" ) - http_client = AzureMLEndpointClient(endpoint_url, endpoint_key, deployment_name) + values["endpoint_api_type"] = get_from_dict_or_env( + values, + "endpoint_api_type", + "AZUREML_ENDPOINT_API_TYPE", + AzureMLEndpointApiType.realtime, + ) + + return values + + @validator("content_formatter") + def validate_content_formatter( + cls, field_value: Any, values: Dict + ) -> ContentFormatterBase: + """Validate that content formatter is supported by endpoint type.""" + endpoint_api_type = values.get("endpoint_api_type") + if endpoint_api_type not in field_value.supported_api_types: + raise ValueError( + f"Content formatter f{type(field_value)} is not supported by this " + f"endpoint. Supported types are {field_value.supported_api_types} " + f"but endpoint is {endpoint_api_type}." + ) + return field_value + + @validator("endpoint_url") + def validate_endpoint_url(cls, field_value: Any) -> str: + """Validate that endpoint url is complete.""" + if field_value.endswith("/"): + field_value = field_value[:-1] + if field_value.endswith("inference.ml.azure.com"): + raise ValueError( + "`endpoint_url` should contain the full invocation URL including " + "`/score` for `endpoint_api_type='realtime'` or `/v1/completions` " + "or `/v1/chat/completions` for `endpoint_api_type='serverless'`" + ) + return field_value + + @validator("endpoint_api_type") + def validate_endpoint_api_type( + cls, field_value: Any, values: Dict + ) -> AzureMLEndpointApiType: + """Validate that endpoint api type is compatible with the URL format.""" + endpoint_url = values.get("endpoint_url") + if field_value == AzureMLEndpointApiType.realtime and not endpoint_url.endswith( + "/score" + ): + raise ValueError( + "Endpoints of type `realtime` should follow the format " + "`https://<your-endpoint>.<your_region>.inference.ml.azure.com/score`." + " If your endpoint URL ends with `/v1/completions` or" + "`/v1/chat/completions`, use `endpoint_api_type='serverless'` instead." + ) + if field_value == AzureMLEndpointApiType.serverless and not ( + endpoint_url.endswith("/v1/completions") + or endpoint_url.endswith("/v1/chat/completions") + ): + raise ValueError( + "Endpoints of type `serverless` should follow the format " + "`https://<your-endpoint>.<your_region>.inference.ml.azure.com/v1/chat/completions`" + " or `https://<your-endpoint>.<your_region>.inference.ml.azure.com/v1/chat/completions`" + ) + + return field_value + + @validator("http_client", always=True) + def validate_client(cls, field_value: Any, values: Dict) -> AzureMLEndpointClient: + """Validate that api key and python package exists in environment.""" + endpoint_url = values.get("endpoint_url") + endpoint_key = values.get("endpoint_api_key") + deployment_name = values.get("deployment_name") + + http_client = AzureMLEndpointClient( + endpoint_url, endpoint_key.get_secret_value(), deployment_name + ) return http_client + +class AzureMLOnlineEndpoint(BaseLLM, AzureMLBaseEndpoint): + """Azure ML Online Endpoint models. + + Example: + .. code-block:: python + azure_llm = AzureMLOnlineEndpoint( + endpoint_url="https://<your-endpoint>.<your_region>.inference.ml.azure.com/score", + endpoint_api_type=AzureMLApiType.realtime, + endpoint_api_key="my-api-key", + content_formatter=content_formatter, + ) + """ # noqa: E501 + @property def _identifying_params(self) -> Mapping[str, Any]: """Get the identifying parameters.""" @@ -262,16 +458,17 @@ def _llm_type(self) -> str: """Return type of llm.""" return "azureml_endpoint" - def _call( + def _generate( self, - prompt: str, + prompts: List[str], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, - ) -> str: - """Call out to an AzureML Managed Online endpoint. + ) -> LLMResult: + """Run the LLM on the given prompts. + Args: - prompt: The prompt to pass into the model. + prompts: The prompt to pass into the model. stop: Optional list of stop words to use when generating. Returns: The string generated by the model. @@ -280,12 +477,21 @@ def _call( response = azureml_model("Tell me a joke.") """ _model_kwargs = self.model_kwargs or {} + _model_kwargs.update(kwargs) + if stop: + _model_kwargs["stop"] = stop + generations = [] + + for prompt in prompts: + request_payload = self.content_formatter.format_request_payload( + prompt, _model_kwargs, self.endpoint_api_type + ) + response_payload = self.http_client.call( + body=request_payload, run_manager=run_manager + ) + generated_text = self.content_formatter.format_response_payload( + response_payload, self.endpoint_api_type + ) + generations.append([generated_text]) - request_payload = self.content_formatter.format_request_payload( - prompt, _model_kwargs - ) - response_payload = self.http_client.call(request_payload, **kwargs) - generated_text = self.content_formatter.format_response_payload( - response_payload - ) - return generated_text + return LLMResult(generations=generations) diff --git a/libs/community/tests/integration_tests/chat_models/test_azureml_endpoint.py b/libs/community/tests/integration_tests/chat_models/test_azureml_endpoint.py index d8871e784f010..31092d625ba75 100644 --- a/libs/community/tests/integration_tests/chat_models/test_azureml_endpoint.py +++ b/libs/community/tests/integration_tests/chat_models/test_azureml_endpoint.py @@ -5,31 +5,31 @@ from langchain_community.chat_models.azureml_endpoint import ( AzureMLChatOnlineEndpoint, - LlamaContentFormatter, + LlamaChatContentFormatter, ) def test_llama_call() -> None: """Test valid call to Open Source Foundation Model.""" - chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaContentFormatter()) - response = chat(messages=[HumanMessage(content="Foo")]) + chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaChatContentFormatter()) + response = chat.invoke([HumanMessage(content="Foo")]) assert isinstance(response, BaseMessage) assert isinstance(response.content, str) -def test_timeout_kwargs() -> None: +def test_temperature_kwargs() -> None: """Test that timeout kwarg works.""" - chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaContentFormatter()) - response = chat(messages=[HumanMessage(content="FOO")], timeout=60) + chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaChatContentFormatter()) + response = chat.invoke([HumanMessage(content="FOO")], temperature=0.8) assert isinstance(response, BaseMessage) assert isinstance(response.content, str) def test_message_history() -> None: """Test that multiple messages works.""" - chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaContentFormatter()) - response = chat( - messages=[ + chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaChatContentFormatter()) + response = chat.invoke( + [ HumanMessage(content="Hello."), AIMessage(content="Hello!"), HumanMessage(content="How are you doing?"), @@ -40,7 +40,7 @@ def test_message_history() -> None: def test_multiple_messages() -> None: - chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaContentFormatter()) + chat = AzureMLChatOnlineEndpoint(content_formatter=LlamaChatContentFormatter()) message = HumanMessage(content="Hi!") response = chat.generate([[message], [message]]) diff --git a/libs/community/tests/integration_tests/llms/test_azureml_endpoint.py b/libs/community/tests/integration_tests/llms/test_azureml_endpoint.py index a0562e27a14a4..4d0e86b510257 100644 --- a/libs/community/tests/integration_tests/llms/test_azureml_endpoint.py +++ b/libs/community/tests/integration_tests/llms/test_azureml_endpoint.py @@ -7,6 +7,7 @@ from urllib.request import HTTPError import pytest +from langchain_core.pydantic_v1 import ValidationError from langchain_community.llms.azureml_endpoint import ( AzureMLOnlineEndpoint, @@ -26,7 +27,7 @@ def test_gpt2_call() -> None: deployment_name=os.getenv("OSS_DEPLOYMENT_NAME"), content_formatter=OSSContentFormatter(), ) - output = llm("Foo") + output = llm.invoke("Foo") assert isinstance(output, str) @@ -38,7 +39,7 @@ def test_hf_call() -> None: deployment_name=os.getenv("HF_DEPLOYMENT_NAME"), content_formatter=HFContentFormatter(), ) - output = llm("Foo") + output = llm.invoke("Foo") assert isinstance(output, str) @@ -50,7 +51,7 @@ def test_dolly_call() -> None: deployment_name=os.getenv("DOLLY_DEPLOYMENT_NAME"), content_formatter=DollyContentFormatter(), ) - output = llm("Foo") + output = llm.invoke("Foo") assert isinstance(output, str) @@ -81,7 +82,7 @@ def format_response_payload(self, output: bytes) -> str: deployment_name=os.getenv("BART_DEPLOYMENT_NAME"), content_formatter=CustomFormatter(), ) - output = llm("Foo") + output = llm.invoke("Foo") assert isinstance(output, str) @@ -93,7 +94,7 @@ def test_missing_content_formatter() -> None: endpoint_url=os.getenv("OSS_ENDPOINT_URL"), deployment_name=os.getenv("OSS_DEPLOYMENT_NAME"), ) - llm("Foo") + llm.invoke("Foo") def test_invalid_request_format() -> None: @@ -123,7 +124,31 @@ def format_response_payload(self, output: bytes) -> str: deployment_name=os.getenv("OSS_DEPLOYMENT_NAME"), content_formatter=CustomContentFormatter(), ) - llm("Foo") + llm.invoke("Foo") + + +def test_incorrect_url() -> None: + """Testing AzureML Endpoint for an incorrect URL""" + with pytest.raises(ValidationError): + llm = AzureMLOnlineEndpoint( + endpoint_api_key=os.getenv("OSS_ENDPOINT_API_KEY"), + endpoint_url="https://endpoint.inference.com", + deployment_name=os.getenv("OSS_DEPLOYMENT_NAME"), + content_formatter=OSSContentFormatter(), + ) + llm.invoke("Foo") + + +def test_incorrect_api_type() -> None: + with pytest.raises(ValidationError): + llm = AzureMLOnlineEndpoint( + endpoint_api_key=os.getenv("OSS_ENDPOINT_API_KEY"), + endpoint_url=os.getenv("OSS_ENDPOINT_URL"), + deployment_name=os.getenv("OSS_DEPLOYMENT_NAME"), + endpoint_api_type="serverless", + content_formatter=OSSContentFormatter(), + ) + llm.invoke("Foo") def test_incorrect_key() -> None: @@ -135,7 +160,7 @@ def test_incorrect_key() -> None: deployment_name=os.getenv("OSS_DEPLOYMENT_NAME"), content_formatter=OSSContentFormatter(), ) - llm("Foo") + llm.invoke("Foo") def test_saving_loading_llm(tmp_path: Path) -> None: From 90f5a1c40e1bd8c4bbb40485dc46ac76af89fee1 Mon Sep 17 00:00:00 2001 From: Serena Ruan <82044803+serena-ruan@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:16:51 -0800 Subject: [PATCH 196/215] community[minor]: Improve mlflow callback (#15691) - **Description:** Allow passing run_id to MLflowCallbackHandler to resume a run instead of creating a new run. Support recording retriever relevant metrics. Refactor the code to fix some bugs. --------- Signed-off-by: Serena Ruan <serena.rxy@gmail.com> --- .../callbacks/mlflow_callback.py | 346 +++++++++++------- 1 file changed, 223 insertions(+), 123 deletions(-) diff --git a/libs/community/langchain_community/callbacks/mlflow_callback.py b/libs/community/langchain_community/callbacks/mlflow_callback.py index 6d93125d564de..577532a361684 100644 --- a/libs/community/langchain_community/callbacks/mlflow_callback.py +++ b/libs/community/langchain_community/callbacks/mlflow_callback.py @@ -1,3 +1,4 @@ +import logging import os import random import string @@ -5,10 +6,11 @@ import traceback from copy import deepcopy from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Sequence, Union from langchain_core.agents import AgentAction, AgentFinish from langchain_core.callbacks import BaseCallbackHandler +from langchain_core.documents import Document from langchain_core.outputs import LLMResult from langchain_core.utils import get_from_dict_or_env @@ -21,6 +23,8 @@ import_textstat, ) +logger = logging.getLogger(__name__) + def import_mlflow() -> Any: """Import the mlflow python package and raise an error if it is not installed.""" @@ -34,6 +38,47 @@ def import_mlflow() -> Any: return mlflow +def mlflow_callback_metrics() -> List[str]: + return [ + "step", + "starts", + "ends", + "errors", + "text_ctr", + "chain_starts", + "chain_ends", + "llm_starts", + "llm_ends", + "llm_streams", + "tool_starts", + "tool_ends", + "agent_ends", + "retriever_starts", + "retriever_ends", + ] + + +def get_text_complexity_metrics() -> List[str]: + return [ + "flesch_reading_ease", + "flesch_kincaid_grade", + "smog_index", + "coleman_liau_index", + "automated_readability_index", + "dale_chall_readability_score", + "difficult_words", + "linsear_write_formula", + "gunning_fog", + # "text_standard" + "fernandez_huerta", + "szigriszt_pazos", + "gutierrez_polini", + "crawford", + "gulpease_index", + "osman", + ] + + def analyze_text( text: str, nlp: Any = None, @@ -52,22 +97,7 @@ def analyze_text( textstat = import_textstat() spacy = import_spacy() text_complexity_metrics = { - "flesch_reading_ease": textstat.flesch_reading_ease(text), - "flesch_kincaid_grade": textstat.flesch_kincaid_grade(text), - "smog_index": textstat.smog_index(text), - "coleman_liau_index": textstat.coleman_liau_index(text), - "automated_readability_index": textstat.automated_readability_index(text), - "dale_chall_readability_score": textstat.dale_chall_readability_score(text), - "difficult_words": textstat.difficult_words(text), - "linsear_write_formula": textstat.linsear_write_formula(text), - "gunning_fog": textstat.gunning_fog(text), - # "text_standard": textstat.text_standard(text), - "fernandez_huerta": textstat.fernandez_huerta(text), - "szigriszt_pazos": textstat.szigriszt_pazos(text), - "gutierrez_polini": textstat.gutierrez_polini(text), - "crawford": textstat.crawford(text), - "gulpease_index": textstat.gulpease_index(text), - "osman": textstat.osman(text), + key: getattr(textstat, key)(text) for key in get_text_complexity_metrics() } resp.update({"text_complexity_metrics": text_complexity_metrics}) resp.update(text_complexity_metrics) @@ -140,58 +170,64 @@ def __init__(self, **kwargs: Any): ) self.mlflow.set_tracking_uri(tracking_uri) - # User can set other env variables described here - # > https://www.mlflow.org/docs/latest/tracking.html#logging-to-a-tracking-server - - experiment_name = get_from_dict_or_env( - kwargs, "experiment_name", "MLFLOW_EXPERIMENT_NAME" - ) - self.mlf_exp = self.mlflow.get_experiment_by_name(experiment_name) - if self.mlf_exp is not None: - self.mlf_expid = self.mlf_exp.experiment_id + if run_id := kwargs.get("run_id"): + self.mlf_expid = self.mlflow.get_run(run_id).info.experiment_id else: - self.mlf_expid = self.mlflow.create_experiment(experiment_name) - - self.start_run(kwargs["run_name"], kwargs["run_tags"]) + # User can set other env variables described here + # > https://www.mlflow.org/docs/latest/tracking.html#logging-to-a-tracking-server - def start_run(self, name: str, tags: Dict[str, str]) -> None: - """To start a new run, auto generates the random suffix for name""" - if name.endswith("-%"): - rname = "".join(random.choices(string.ascii_uppercase + string.digits, k=7)) - name = name.replace("%", rname) - self.run = self.mlflow.MlflowClient().create_run( - self.mlf_expid, run_name=name, tags=tags + experiment_name = get_from_dict_or_env( + kwargs, "experiment_name", "MLFLOW_EXPERIMENT_NAME" + ) + self.mlf_exp = self.mlflow.get_experiment_by_name(experiment_name) + if self.mlf_exp is not None: + self.mlf_expid = self.mlf_exp.experiment_id + else: + self.mlf_expid = self.mlflow.create_experiment(experiment_name) + + self.start_run( + kwargs["run_name"], kwargs["run_tags"], kwargs.get("run_id", None) ) + self.dir = kwargs.get("artifacts_dir", "") + + def start_run( + self, name: str, tags: Dict[str, str], run_id: Optional[str] = None + ) -> None: + """ + If run_id is provided, it will reuse the run with the given run_id. + Otherwise, it starts a new run, auto generates the random suffix for name. + """ + if run_id is None: + if name.endswith("-%"): + rname = "".join( + random.choices(string.ascii_uppercase + string.digits, k=7) + ) + name = name[:-1] + rname + run = self.mlflow.MlflowClient().create_run( + self.mlf_expid, run_name=name, tags=tags + ) + run_id = run.info.run_id + self.run_id = run_id def finish_run(self) -> None: """To finish the run.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.end_run() + self.mlflow.end_run() def metric(self, key: str, value: float) -> None: """To log metric to mlflow server.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.log_metric(key, value) + self.mlflow.log_metric(key, value, run_id=self.run_id) def metrics( self, data: Union[Dict[str, float], Dict[str, int]], step: Optional[int] = 0 ) -> None: """To log all metrics in the input dict.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.log_metrics(data) + self.mlflow.log_metrics(data, run_id=self.run_id) def jsonf(self, data: Dict[str, Any], filename: str) -> None: """To log the input data as json file artifact.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.log_dict(data, f"{filename}.json") + self.mlflow.log_dict( + data, os.path.join(self.dir, f"{filename}.json"), run_id=self.run_id + ) def table(self, name: str, dataframe) -> None: # type: ignore """To log the input pandas dataframe as a html table""" @@ -199,30 +235,22 @@ def table(self, name: str, dataframe) -> None: # type: ignore def html(self, html: str, filename: str) -> None: """To log the input html string as html file artifact.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.log_text(html, f"{filename}.html") + self.mlflow.log_text( + html, os.path.join(self.dir, f"{filename}.html"), run_id=self.run_id + ) def text(self, text: str, filename: str) -> None: """To log the input text as text file artifact.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.log_text(text, f"{filename}.txt") + self.mlflow.log_text( + text, os.path.join(self.dir, f"{filename}.txt"), run_id=self.run_id + ) def artifact(self, path: str) -> None: """To upload the file from given path as artifact.""" - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.log_artifact(path) + self.mlflow.log_artifact(path, run_id=self.run_id) def langchain_artifact(self, chain: Any) -> None: - with self.mlflow.start_run( - run_id=self.run.info.run_id, experiment_id=self.mlf_expid - ): - self.mlflow.langchain.log_model(chain, "langchain-model") + self.mlflow.langchain.log_model(chain, "langchain-model", run_id=self.run_id) class MlflowCallbackHandler(BaseMetadataCallbackHandler, BaseCallbackHandler): @@ -246,6 +274,8 @@ def __init__( experiment: Optional[str] = "langchain", tags: Optional[Dict] = None, tracking_uri: Optional[str] = None, + run_id: Optional[str] = None, + artifacts_dir: Optional[str] = None, ) -> None: """Initialize callback handler.""" import_pandas() @@ -258,6 +288,8 @@ def __init__( self.experiment = experiment self.tags = tags or {} self.tracking_uri = tracking_uri + self.run_id = run_id + self.artifacts_dir = artifacts_dir self.temp_dir = tempfile.TemporaryDirectory() @@ -266,26 +298,21 @@ def __init__( experiment_name=self.experiment, run_name=self.name, run_tags=self.tags, + run_id=self.run_id, + artifacts_dir=self.artifacts_dir, ) self.action_records: list = [] - self.nlp = spacy.load("en_core_web_sm") - - self.metrics = { - "step": 0, - "starts": 0, - "ends": 0, - "errors": 0, - "text_ctr": 0, - "chain_starts": 0, - "chain_ends": 0, - "llm_starts": 0, - "llm_ends": 0, - "llm_streams": 0, - "tool_starts": 0, - "tool_ends": 0, - "agent_ends": 0, - } + try: + self.nlp = spacy.load("en_core_web_sm") + except OSError: + logger.warning( + "Run `python -m spacy download en_core_web_sm` " + "to download en_core_web_sm model for text visualization." + ) + self.nlp = None + + self.metrics = {key: 0 for key in mlflow_callback_metrics()} self.records: Dict[str, Any] = { "on_llm_start_records": [], @@ -298,6 +325,8 @@ def __init__( "on_text_records": [], "on_agent_finish_records": [], "on_agent_action_records": [], + "on_retriever_start_records": [], + "on_retriever_end_records": [], "action_records": [], } @@ -383,10 +412,14 @@ def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: self.records["on_llm_end_records"].append(generation_resp) self.records["action_records"].append(generation_resp) self.mlflg.jsonf(resp, f"llm_end_{llm_ends}_generation_{idx}") - dependency_tree = generation_resp["dependency_tree"] - entities = generation_resp["entities"] - self.mlflg.html(dependency_tree, "dep-" + hash_string(generation.text)) - self.mlflg.html(entities, "ent-" + hash_string(generation.text)) + if "dependency_tree" in generation_resp: + dependency_tree = generation_resp["dependency_tree"] + self.mlflg.html( + dependency_tree, "dep-" + hash_string(generation.text) + ) + if "entities" in generation_resp: + entities = generation_resp["entities"] + self.mlflg.html(entities, "ent-" + hash_string(generation.text)) def on_llm_error(self, error: BaseException, **kwargs: Any) -> None: """Run when LLM errors.""" @@ -410,14 +443,21 @@ def on_chain_start( self.mlflg.metrics(self.metrics, step=self.metrics["step"]) - chain_input = ",".join([f"{k}={v}" for k, v in inputs.items()]) + if isinstance(inputs, dict): + chain_input = ",".join([f"{k}={v}" for k, v in inputs.items()]) + elif isinstance(inputs, list): + chain_input = ",".join([str(input) for input in inputs]) + else: + chain_input = str(inputs) input_resp = deepcopy(resp) input_resp["inputs"] = chain_input self.records["on_chain_start_records"].append(input_resp) self.records["action_records"].append(input_resp) self.mlflg.jsonf(input_resp, f"chain_start_{chain_starts}") - def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None: + def on_chain_end( + self, outputs: Union[Dict[str, Any], str, List[str]], **kwargs: Any + ) -> None: """Run when chain ends running.""" self.metrics["step"] += 1 self.metrics["chain_ends"] += 1 @@ -426,7 +466,12 @@ def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None: chain_ends = self.metrics["chain_ends"] resp: Dict[str, Any] = {} - chain_output = ",".join([f"{k}={v}" for k, v in outputs.items()]) + if isinstance(outputs, dict): + chain_output = ",".join([f"{k}={v}" for k, v in outputs.items()]) + elif isinstance(outputs, list): + chain_output = ",".join(map(str, outputs)) + else: + chain_output = str(outputs) resp.update({"action": "on_chain_end", "outputs": chain_output}) resp.update(self.metrics) @@ -487,7 +532,7 @@ def on_tool_error(self, error: BaseException, **kwargs: Any) -> None: def on_text(self, text: str, **kwargs: Any) -> None: """ - Run when agent is ending. + Run when text is received. """ self.metrics["step"] += 1 self.metrics["text_ctr"] += 1 @@ -549,6 +594,69 @@ def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any: self.records["action_records"].append(resp) self.mlflg.jsonf(resp, f"agent_action_{tool_starts}") + def on_retriever_start( + self, + serialized: Dict[str, Any], + query: str, + **kwargs: Any, + ) -> Any: + """Run when Retriever starts running.""" + self.metrics["step"] += 1 + self.metrics["retriever_starts"] += 1 + self.metrics["starts"] += 1 + + retriever_starts = self.metrics["retriever_starts"] + + resp: Dict[str, Any] = {} + resp.update({"action": "on_retriever_start", "query": query}) + resp.update(flatten_dict(serialized)) + resp.update(self.metrics) + + self.mlflg.metrics(self.metrics, step=self.metrics["step"]) + + self.records["on_retriever_start_records"].append(resp) + self.records["action_records"].append(resp) + self.mlflg.jsonf(resp, f"retriever_start_{retriever_starts}") + + def on_retriever_end( + self, + documents: Sequence[Document], + **kwargs: Any, + ) -> Any: + """Run when Retriever ends running.""" + self.metrics["step"] += 1 + self.metrics["retriever_ends"] += 1 + self.metrics["ends"] += 1 + + retriever_ends = self.metrics["retriever_ends"] + + resp: Dict[str, Any] = {} + retriever_documents = [ + { + "page_content": doc.page_content, + "metadata": { + k: str(v) + if not isinstance(v, list) + else ",".join(str(x) for x in v) + for k, v in doc.metadata.items() + }, + } + for doc in documents + ] + resp.update({"action": "on_retriever_end", "documents": retriever_documents}) + resp.update(self.metrics) + + self.mlflg.metrics(self.metrics, step=self.metrics["step"]) + + self.records["on_retriever_end_records"].append(resp) + self.records["action_records"].append(resp) + self.mlflg.jsonf(resp, f"retriever_end_{retriever_ends}") + + def on_retriever_error(self, error: BaseException, **kwargs: Any) -> Any: + """Run when Retriever errors.""" + self.metrics["step"] += 1 + self.metrics["errors"] += 1 + def _create_session_analysis_df(self) -> Any: """Create a dataframe with all the information from the session.""" pd = import_pandas() @@ -570,39 +678,27 @@ def _create_session_analysis_df(self) -> Any: .dropna(axis=1) .rename({"step": "prompt_step"}, axis=1) ) - complexity_metrics_columns = [] - visualizations_columns = [] - - complexity_metrics_columns = [ - "flesch_reading_ease", - "flesch_kincaid_grade", - "smog_index", - "coleman_liau_index", - "automated_readability_index", - "dale_chall_readability_score", - "difficult_words", - "linsear_write_formula", - "gunning_fog", - # "text_standard", - "fernandez_huerta", - "szigriszt_pazos", - "gutierrez_polini", - "crawford", - "gulpease_index", - "osman", - ] + complexity_metrics_columns = get_text_complexity_metrics() + visualizations_columns = ( + ["dependency_tree", "entities"] if self.nlp is not None else [] + ) - visualizations_columns = ["dependency_tree", "entities"] + token_usage_columns = [ + "token_usage_total_tokens", + "token_usage_prompt_tokens", + "token_usage_completion_tokens", + ] + token_usage_columns = [ + x for x in token_usage_columns if x in on_llm_end_records_df.columns + ] llm_outputs_df = ( on_llm_end_records_df[ [ "step", "text", - "token_usage_total_tokens", - "token_usage_prompt_tokens", - "token_usage_completion_tokens", ] + + token_usage_columns + complexity_metrics_columns + visualizations_columns ] @@ -620,14 +716,18 @@ def _create_session_analysis_df(self) -> Any: ) return session_analysis_df + def _contain_llm_records(self): + return bool(self.records["on_llm_start_records"]) + def flush_tracker(self, langchain_asset: Any = None, finish: bool = False) -> None: pd = import_pandas() self.mlflg.table("action_records", pd.DataFrame(self.records["action_records"])) - session_analysis_df = self._create_session_analysis_df() - chat_html = session_analysis_df.pop("chat_html") - chat_html = chat_html.replace("\n", "", regex=True) - self.mlflg.table("session_analysis", pd.DataFrame(session_analysis_df)) - self.mlflg.html("".join(chat_html.tolist()), "chat_html") + if self._contain_llm_records(): + session_analysis_df = self._create_session_analysis_df() + chat_html = session_analysis_df.pop("chat_html") + chat_html = chat_html.replace("\n", "", regex=True) + self.mlflg.table("session_analysis", pd.DataFrame(session_analysis_df)) + self.mlflg.html("".join(chat_html.tolist()), "chat_html") if langchain_asset: # To avoid circular import error From e135e5257c4ed8185308b976a1dde1f0951d1324 Mon Sep 17 00:00:00 2001 From: Noah Stapp <noah.stapp@mongodb.com> Date: Tue, 23 Jan 2024 18:18:28 -0800 Subject: [PATCH 197/215] community[patch]: Include scores in MongoDB Atlas QA chain results (#14666) Adds the ability to return similarity scores when using `RetrievalQA.from_chain_type` with `MongoDBAtlasVectorSearch`. Requires that `return_source_documents=True` is set. Example use: ``` vector_search = MongoDBAtlasVectorSearch.from_documents(...) qa = RetrievalQA.from_chain_type( llm=OpenAI(), chain_type="stuff", retriever=vector_search.as_retriever(search_kwargs={"additional": ["similarity_score"]}), return_source_documents=True ) ... docs = qa({"query": "..."}) docs["source_documents"][0].metadata["score"] # score will be here ``` I've tested this feature locally, using a MongoDB Atlas Cluster with a vector search index. --- .../vectorstores/mongodb_atlas.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/libs/community/langchain_community/vectorstores/mongodb_atlas.py b/libs/community/langchain_community/vectorstores/mongodb_atlas.py index f91071ac865ca..c105e6f536f35 100644 --- a/libs/community/langchain_community/vectorstores/mongodb_atlas.py +++ b/libs/community/langchain_community/vectorstores/mongodb_atlas.py @@ -209,6 +209,7 @@ def _similarity_search_with_score( for res in cursor: text = res.pop(self._text_key) score = res.pop("score") + del res["embedding"] docs.append((Document(page_content=text, metadata=res), score)) return docs @@ -221,11 +222,8 @@ def similarity_search_with_score( ) -> List[Tuple[Document, float]]: """Return MongoDB documents most similar to the given query and their scores. - Uses the $vectorSearch stage - performs aNN search on a vector in the specified field. - Index the field as "vector" using Atlas Vector Search "vectorSearch" index type - - For more info : https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ + Uses the vectorSearch operator available in MongoDB Atlas Search. + For more: https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ Args: query: Text to look up documents similar to. @@ -233,7 +231,7 @@ def similarity_search_with_score( pre_filter: (Optional) dictionary of argument(s) to prefilter document fields on. post_filter_pipeline: (Optional) Pipeline of MongoDB aggregation stages - following the vector Search. + following the vectorSearch stage. Returns: List of documents most similar to the query and their scores. @@ -257,11 +255,8 @@ def similarity_search( ) -> List[Document]: """Return MongoDB documents most similar to the given query. - Uses the $vectorSearch stage - performs aNN search on a vector in the specified field. - Index the field as "vector" using Atlas Vector Search "vectorSearch" index type - - For more info : https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ + Uses the vectorSearch operator available in MongoDB Atlas Search. + For more: https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ Args: query: Text to look up documents similar to. @@ -269,17 +264,22 @@ def similarity_search( pre_filter: (Optional) dictionary of argument(s) to prefilter document fields on. post_filter_pipeline: (Optional) Pipeline of MongoDB aggregation stages - following the vector search. + following the vectorSearch stage. Returns: List of documents most similar to the query and their scores. """ + additional = kwargs.get("additional") docs_and_scores = self.similarity_search_with_score( query, k=k, pre_filter=pre_filter, post_filter_pipeline=post_filter_pipeline, ) + + if additional and "similarity_score" in additional: + for doc, score in docs_and_scores: + doc.metadata["score"] = score return [doc for doc, _ in docs_and_scores] def max_marginal_relevance_search( @@ -309,7 +309,7 @@ def max_marginal_relevance_search( pre_filter: (Optional) dictionary of argument(s) to prefilter on document fields. post_filter_pipeline: (Optional) pipeline of MongoDB aggregation stages - following the vector search. + following the vectorSearch stage. Returns: List of documents selected by maximal marginal relevance. """ From 95ee69a301621ba4ea23db752777c936ceef1426 Mon Sep 17 00:00:00 2001 From: i-w-a <65731397+i-w-a@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:20:29 +0900 Subject: [PATCH 198/215] langchain[patch]: In HTMLHeaderTextSplitter set default encoding to utf-8 (#16372) - **Description:** The HTMLHeaderTextSplitter Class now explicitly specifies utf-8 encoding in the part of the split_text_from_file method that calls the HTMLParser. - **Issue:** Prevent garbled characters due to differences in encoding of html files (except for English in particular, I noticed that problem with Japanese). - **Dependencies:** No dependencies, - **Twitter handle:** @i_w__a --- libs/langchain/langchain/text_splitter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/langchain/langchain/text_splitter.py b/libs/langchain/langchain/text_splitter.py index cd6204adfc8b6..c4ece25320455 100644 --- a/libs/langchain/langchain/text_splitter.py +++ b/libs/langchain/langchain/text_splitter.py @@ -598,7 +598,9 @@ def split_text_from_file(self, file: Any) -> List[Document]: "Unable to import lxml, please install with `pip install lxml`." ) from e # use lxml library to parse html document and return xml ElementTree - parser = etree.HTMLParser() + # Explicitly encoding in utf-8 allows non-English + # html files to be processed without garbled characters + parser = etree.HTMLParser(encoding="utf-8") tree = etree.parse(file, parser) # document transformation for "structure-aware" chunking is handled with xsl. From c69f599594ba16b83d088ed33e7615ce27c872f0 Mon Sep 17 00:00:00 2001 From: Gianfranco Demarco <36262716+gianfrancodemarco@users.noreply.github.com> Date: Wed, 24 Jan 2024 03:22:09 +0100 Subject: [PATCH 199/215] langchain[patch]: Extract _aperform_agent_action from _aiter_next_step from AgentExecutor (#15707) - **Description:** extreact the _aperform_agent_action in the AgentExecutor class to allow for easier overriding. Extracted logic from _iter_next_step into a new method _perform_agent_action for consistency and easier overriding. - **Issue:** #15706 Closes #15706 --- libs/langchain/langchain/agents/agent.py | 158 +++++++++++++---------- 1 file changed, 89 insertions(+), 69 deletions(-) diff --git a/libs/langchain/langchain/agents/agent.py b/libs/langchain/langchain/agents/agent.py index 187a3ba0e61a4..8bd7b6d478ab5 100644 --- a/libs/langchain/langchain/agents/agent.py +++ b/libs/langchain/langchain/agents/agent.py @@ -1179,37 +1179,48 @@ def _iter_next_step( for agent_action in actions: yield agent_action for agent_action in actions: - if run_manager: - run_manager.on_agent_action(agent_action, color="green") - # Otherwise we lookup the tool - if agent_action.tool in name_to_tool_map: - tool = name_to_tool_map[agent_action.tool] - return_direct = tool.return_direct - color = color_mapping[agent_action.tool] - tool_run_kwargs = self.agent.tool_run_logging_kwargs() - if return_direct: - tool_run_kwargs["llm_prefix"] = "" - # We then call the tool on the tool input to get an observation - observation = tool.run( - agent_action.tool_input, - verbose=self.verbose, - color=color, - callbacks=run_manager.get_child() if run_manager else None, - **tool_run_kwargs, - ) - else: - tool_run_kwargs = self.agent.tool_run_logging_kwargs() - observation = InvalidTool().run( - { - "requested_tool_name": agent_action.tool, - "available_tool_names": list(name_to_tool_map.keys()), - }, - verbose=self.verbose, - color=None, - callbacks=run_manager.get_child() if run_manager else None, - **tool_run_kwargs, - ) - yield AgentStep(action=agent_action, observation=observation) + yield self._perform_agent_action( + name_to_tool_map, color_mapping, agent_action, run_manager + ) + + def _perform_agent_action( + self, + name_to_tool_map: Dict[str, BaseTool], + color_mapping: Dict[str, str], + agent_action: AgentAction, + run_manager: Optional[CallbackManagerForChainRun] = None, + ) -> AgentStep: + if run_manager: + run_manager.on_agent_action(agent_action, color="green") + # Otherwise we lookup the tool + if agent_action.tool in name_to_tool_map: + tool = name_to_tool_map[agent_action.tool] + return_direct = tool.return_direct + color = color_mapping[agent_action.tool] + tool_run_kwargs = self.agent.tool_run_logging_kwargs() + if return_direct: + tool_run_kwargs["llm_prefix"] = "" + # We then call the tool on the tool input to get an observation + observation = tool.run( + agent_action.tool_input, + verbose=self.verbose, + color=color, + callbacks=run_manager.get_child() if run_manager else None, + **tool_run_kwargs, + ) + else: + tool_run_kwargs = self.agent.tool_run_logging_kwargs() + observation = InvalidTool().run( + { + "requested_tool_name": agent_action.tool, + "available_tool_names": list(name_to_tool_map.keys()), + }, + verbose=self.verbose, + color=None, + callbacks=run_manager.get_child() if run_manager else None, + **tool_run_kwargs, + ) + return AgentStep(action=agent_action, observation=observation) async def _atake_next_step( self, @@ -1303,52 +1314,61 @@ async def _aiter_next_step( for agent_action in actions: yield agent_action - async def _aperform_agent_action( - agent_action: AgentAction, - ) -> AgentStep: - if run_manager: - await run_manager.on_agent_action( - agent_action, verbose=self.verbose, color="green" - ) - # Otherwise we lookup the tool - if agent_action.tool in name_to_tool_map: - tool = name_to_tool_map[agent_action.tool] - return_direct = tool.return_direct - color = color_mapping[agent_action.tool] - tool_run_kwargs = self.agent.tool_run_logging_kwargs() - if return_direct: - tool_run_kwargs["llm_prefix"] = "" - # We then call the tool on the tool input to get an observation - observation = await tool.arun( - agent_action.tool_input, - verbose=self.verbose, - color=color, - callbacks=run_manager.get_child() if run_manager else None, - **tool_run_kwargs, - ) - else: - tool_run_kwargs = self.agent.tool_run_logging_kwargs() - observation = await InvalidTool().arun( - { - "requested_tool_name": agent_action.tool, - "available_tool_names": list(name_to_tool_map.keys()), - }, - verbose=self.verbose, - color=None, - callbacks=run_manager.get_child() if run_manager else None, - **tool_run_kwargs, - ) - return AgentStep(action=agent_action, observation=observation) - # Use asyncio.gather to run multiple tool.arun() calls concurrently result = await asyncio.gather( - *[_aperform_agent_action(agent_action) for agent_action in actions] + *[ + self._aperform_agent_action( + name_to_tool_map, color_mapping, agent_action, run_manager + ) + for agent_action in actions + ], ) # TODO This could yield each result as it becomes available for chunk in result: yield chunk + async def _aperform_agent_action( + self, + name_to_tool_map: Dict[str, BaseTool], + color_mapping: Dict[str, str], + agent_action: AgentAction, + run_manager: Optional[AsyncCallbackManagerForChainRun] = None, + ) -> AgentStep: + if run_manager: + await run_manager.on_agent_action( + agent_action, verbose=self.verbose, color="green" + ) + # Otherwise we lookup the tool + if agent_action.tool in name_to_tool_map: + tool = name_to_tool_map[agent_action.tool] + return_direct = tool.return_direct + color = color_mapping[agent_action.tool] + tool_run_kwargs = self.agent.tool_run_logging_kwargs() + if return_direct: + tool_run_kwargs["llm_prefix"] = "" + # We then call the tool on the tool input to get an observation + observation = await tool.arun( + agent_action.tool_input, + verbose=self.verbose, + color=color, + callbacks=run_manager.get_child() if run_manager else None, + **tool_run_kwargs, + ) + else: + tool_run_kwargs = self.agent.tool_run_logging_kwargs() + observation = await InvalidTool().arun( + { + "requested_tool_name": agent_action.tool, + "available_tool_names": list(name_to_tool_map.keys()), + }, + verbose=self.verbose, + color=None, + callbacks=run_manager.get_child() if run_manager else None, + **tool_run_kwargs, + ) + return AgentStep(action=agent_action, observation=observation) + def _call( self, inputs: Dict[str, str], From 4e160540ffcd5da4b9ee2ce79fe4517d72191e37 Mon Sep 17 00:00:00 2001 From: Shivani Modi <shivanimodi16@gmail.com> Date: Tue, 23 Jan 2024 18:22:32 -0800 Subject: [PATCH 200/215] community[minor]: Adding Konko Completion endpoint (#15570) This PR introduces update to Konko Integration with LangChain. 1. **New Endpoint Addition**: Integration of a new endpoint to utilize completion models hosted on Konko. 2. **Chat Model Updates for Backward Compatibility**: We have updated the chat models to ensure backward compatibility with previous OpenAI versions. 4. **Updated Documentation**: Comprehensive documentation has been updated to reflect these new changes, providing clear guidance on utilizing the new features and ensuring seamless integration. Thank you to the LangChain team for their exceptional work and for considering this PR. Please let me know if any additional information is needed. --------- Co-authored-by: Shivani Modi <shivanimodi@Shivanis-MacBook-Pro.local> Co-authored-by: Shivani Modi <shivanimodi@Shivanis-MBP.lan> --- docs/docs/integrations/chat/konko.ipynb | 43 ++-- docs/docs/integrations/llms/konko.ipynb | 100 +++++++++ docs/docs/integrations/providers/konko.mdx | 30 +-- .../langchain_community/chat_models/konko.py | 64 ++---- .../langchain_community/llms/__init__.py | 10 + .../langchain_community/llms/konko.py | 200 ++++++++++++++++++ .../chat_models/test_konko.py | 2 +- .../integration_tests/llms/test_konko.py | 36 ++++ .../tests/unit_tests/chat_models/konko.py | 174 +++++++++++++++ libs/community/tests/unit_tests/llms/konko.py | 36 ++++ .../tests/unit_tests/llms/test_imports.py | 1 + 11 files changed, 622 insertions(+), 74 deletions(-) create mode 100644 docs/docs/integrations/llms/konko.ipynb create mode 100644 libs/community/langchain_community/llms/konko.py create mode 100644 libs/community/tests/integration_tests/llms/test_konko.py create mode 100644 libs/community/tests/unit_tests/chat_models/konko.py create mode 100644 libs/community/tests/unit_tests/llms/konko.py diff --git a/docs/docs/integrations/chat/konko.ipynb b/docs/docs/integrations/chat/konko.ipynb index 95c826a093752..173fd34691a3e 100644 --- a/docs/docs/integrations/chat/konko.ipynb +++ b/docs/docs/integrations/chat/konko.ipynb @@ -21,17 +21,31 @@ "\n", "1. Select the right LLM(s) for their application\n", "2. Prototype with various open-source and proprietary LLMs\n", - "3. Move to production in-line with their security, privacy, throughput, latency SLAs without infrastructure set-up or administration using Konko AI's SOC 2 compliant infrastructure\n", + "3. Access Fine Tuning for open-source LLMs to get industry-leading performance at a fraction of the cost\n", + "4. Setup low-cost production APIs according to security, privacy, throughput, latency SLAs without infrastructure set-up or administration using Konko AI's SOC 2 compliant, multi-cloud infrastructure\n", "\n", + "### Steps to Access Models\n", + "1. **Explore Available Models:** Start by browsing through the [available models](https://docs.konko.ai/docs/list-of-models) on Konko. Each model caters to different use cases and capabilities.\n", "\n", - "This example goes over how to use LangChain to interact with `Konko` [models](https://docs.konko.ai/docs/overview)" + "2. **Identify Suitable Endpoints:** Determine which [endpoint](https://docs.konko.ai/docs/list-of-models#list-of-available-models) (ChatCompletion or Completion) supports your selected model.\n", + "\n", + "3. **Selecting a Model:** [Choose a model](https://docs.konko.ai/docs/list-of-models#list-of-available-models) based on its metadata and how well it fits your use case.\n", + "\n", + "4. **Prompting Guidelines:** Once a model is selected, refer to the [prompting guidelines](https://docs.konko.ai/docs/prompting) to effectively communicate with it.\n", + "\n", + "5. **Using the API:** Finally, use the appropriate Konko [API endpoint](https://docs.konko.ai/docs/quickstart-for-completion-and-chat-completion-endpoint) to call the model and receive responses.\n", + "\n", + "To run this notebook, you'll need Konko API key. You can create one by signing up on [Konko](https://www.konko.ai/).\n", + "\n", + "This example goes over how to use LangChain to interact with `Konko` ChatCompletion [models](https://docs.konko.ai/docs/list-of-models#konko-hosted-models-for-chatcompletion)\n", + "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "To run this notebook, you'll need Konko API key. You can request it by messaging support@konko.ai." + "To run this notebook, you'll need Konko API key. You can create one by signing up on [Konko](https://www.konko.ai/)." ] }, { @@ -84,36 +98,34 @@ "source": [ "## Calling a model\n", "\n", - "Find a model on the [Konko overview page](https://docs.konko.ai/docs/overview)\n", - "\n", - "For example, for this [LLama 2 model](https://docs.konko.ai/docs/meta-llama-2-13b-chat). The model id would be: `\"meta-llama/Llama-2-13b-chat-hf\"`\n", + "Find a model on the [Konko overview page](https://docs.konko.ai/v0.5.0/docs/list-of-models)\n", "\n", - "Another way to find the list of models running on the Konko instance is through this [endpoint](https://docs.konko.ai/reference/listmodels).\n", + "Another way to find the list of models running on the Konko instance is through this [endpoint](https://docs.konko.ai/reference/get-models).\n", "\n", "From here, we can initialize our model:\n" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "chat = ChatKonko(max_tokens=400, model=\"meta-llama/Llama-2-13b-chat-hf\")" + "chat = ChatKonko(max_tokens=400, model=\"meta-llama/llama-2-13b-chat\")" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content=\" Sure, I'd be happy to explain the Big Bang Theory briefly!\\n\\nThe Big Bang Theory is the leading explanation for the origin and evolution of the universe, based on a vast amount of observational evidence from many fields of science. In essence, the theory posits that the universe began as an infinitely hot and dense point, known as a singularity, around 13.8 billion years ago. This singularity expanded rapidly, and as it did, it cooled and formed subatomic particles, which eventually coalesced into the first atoms, and later into the stars and galaxies we see today.\\n\\nThe theory gets its name from the idea that the universe began in a state of incredibly high energy and temperature, and has been expanding and cooling ever since. This expansion is thought to have been driven by a mysterious force known as dark energy, which is thought to be responsible for the accelerating expansion of the universe.\\n\\nOne of the key predictions of the Big Bang Theory is that the universe should be homogeneous and isotropic on large scales, meaning that it should look the same in all directions and have the same properties everywhere. This prediction has been confirmed by a wealth of observational evidence, including the cosmic microwave background radiation, which is thought to be a remnant of the early universe.\\n\\nOverall, the Big Bang Theory is a well-established and widely accepted explanation for the origins of the universe, and it has been supported by a vast amount of observational evidence from many fields of science.\", additional_kwargs={}, example=False)" + "AIMessage(content=\" Sure thing! The Big Bang Theory is a scientific theory that explains the origins of the universe. In short, it suggests that the universe began as an infinitely hot and dense point around 13.8 billion years ago and expanded rapidly. This expansion continues to this day, and it's what makes the universe look the way it does.\\n\\nHere's a brief overview of the key points:\\n\\n1. The universe started as a singularity, a point of infinite density and temperature.\\n2. The singularity expanded rapidly, causing the universe to cool and expand.\\n3. As the universe expanded, particles began to form, including protons, neutrons, and electrons.\\n4. These particles eventually came together to form atoms, and later, stars and galaxies.\\n5. The universe is still expanding today, and the rate of this expansion is accelerating.\\n\\nThat's the Big Bang Theory in a nutshell! It's a pretty mind-blowing idea when you think about it, and it's supported by a lot of scientific evidence. Do you have any other questions about it?\")" ] }, - "execution_count": 7, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -125,13 +137,6 @@ "]\n", "chat(messages)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/docs/integrations/llms/konko.ipynb b/docs/docs/integrations/llms/konko.ipynb new file mode 100644 index 0000000000000..4ef5a7a3281c4 --- /dev/null +++ b/docs/docs/integrations/llms/konko.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "136d9ba6-c42a-435b-9e19-77ebcc7a3145", + "metadata": {}, + "source": [ + "# ChatKonko\n", + "\n", + ">[Konko](https://www.konko.ai/) API is a fully managed Web API designed to help application developers:\n", + "\n", + "Konko API is a fully managed API designed to help application developers:\n", + "\n", + "1. Select the right LLM(s) for their application\n", + "2. Prototype with various open-source and proprietary LLMs\n", + "3. Access Fine Tuning for open-source LLMs to get industry-leading performance at a fraction of the cost\n", + "4. Setup low-cost production APIs according to security, privacy, throughput, latency SLAs without infrastructure set-up or administration using Konko AI's SOC 2 compliant, multi-cloud infrastructure\n" + ] + }, + { + "cell_type": "markdown", + "id": "0d896d07-82b4-4f38-8c37-f0bc8b0e4fe1", + "metadata": {}, + "source": [ + "### Steps to Access Models\n", + "1. **Explore Available Models:** Start by browsing through the [available models](https://docs.konko.ai/docs/list-of-models) on Konko. Each model caters to different use cases and capabilities.\n", + "\n", + "2. **Identify Suitable Endpoints:** Determine which [endpoint](https://docs.konko.ai/docs/list-of-models#list-of-available-models) (ChatCompletion or Completion) supports your selected model.\n", + "\n", + "3. **Selecting a Model:** [Choose a model](https://docs.konko.ai/docs/list-of-models#list-of-available-models) based on its metadata and how well it fits your use case.\n", + "\n", + "4. **Prompting Guidelines:** Once a model is selected, refer to the [prompting guidelines](https://docs.konko.ai/docs/prompting) to effectively communicate with it.\n", + "\n", + "5. **Using the API:** Finally, use the appropriate Konko [API endpoint](https://docs.konko.ai/docs/quickstart-for-completion-and-chat-completion-endpoint) to call the model and receive responses.\n", + "\n", + "This example goes over how to use LangChain to interact with `Konko` completion [models](https://docs.konko.ai/docs/list-of-models#konko-hosted-models-for-completion)\n", + "\n", + "To run this notebook, you'll need Konko API key. You can create one by signing up on [Konko](https://www.konko.ai/)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "dd70bccb-7a65-42d0-a3f2-8116f3549da7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Answer:\n", + "The Big Bang Theory is a theory that explains the origin of the universe. According to the theory, the universe began with a single point of infinite density and temperature. This point is called the singularity. The singularity exploded and expanded rapidly. The expansion of the universe is still continuing.\n", + "The Big Bang Theory is a theory that explains the origin of the universe. According to the theory, the universe began with a single point of infinite density and temperature. This point is called the singularity. The singularity exploded and expanded rapidly. The expansion of the universe is still continuing.\n", + "\n", + "Question\n" + ] + } + ], + "source": [ + "from langchain.llms import Konko\n", + "\n", + "llm = Konko(model=\"mistralai/mistral-7b-v0.1\", temperature=0.1, max_tokens=128)\n", + "\n", + "input_ = \"\"\"You are a helpful assistant. Explain Big Bang Theory briefly.\"\"\"\n", + "print(llm(input_))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78148bf7-2211-40b4-93a7-e90139ab1169", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/integrations/providers/konko.mdx b/docs/docs/integrations/providers/konko.mdx index 1735aa0d01c30..efdc5cb05a480 100644 --- a/docs/docs/integrations/providers/konko.mdx +++ b/docs/docs/integrations/providers/konko.mdx @@ -60,21 +60,27 @@ konko.Model.list() ## Calling a model -Find a model on the [Konko Introduction page](https://docs.konko.ai/docs#available-models) - -For example, for this [LLama 2 model](https://docs.konko.ai/docs/meta-llama-2-13b-chat). The model id would be: `"meta-llama/Llama-2-13b-chat-hf"` +Find a model on the [Konko Introduction page](https://docs.konko.ai/docs/list-of-models) Another way to find the list of models running on the Konko instance is through this [endpoint](https://docs.konko.ai/reference/listmodels). -From here, we can initialize our model: +## Examples of Endpoint Usage -```python -chat_instance = ChatKonko(max_tokens=10, model = 'meta-llama/Llama-2-13b-chat-hf') -``` -And run it: +- **ChatCompletion with Mistral-7B:** + ```python + chat_instance = ChatKonko(max_tokens=10, model = 'mistralai/mistral-7b-instruct-v0.1') + msg = HumanMessage(content="Hi") + chat_response = chat_instance([msg]) + + ``` -```python -msg = HumanMessage(content="Hi") -chat_response = chat_instance([msg]) -``` +- **Completion with mistralai/Mistral-7B-v0.1:** + ```python + from langchain.llms import Konko + llm = Konko(max_tokens=800, model='mistralai/Mistral-7B-v0.1') + prompt = "Generate a Product Description for Apple Iphone 15" + response = llm(prompt) + ``` + +For further assistance, contact [support@konko.ai](mailto:support@konko.ai) or join our [Discord](https://discord.gg/TXV2s3z7RZ). \ No newline at end of file diff --git a/libs/community/langchain_community/chat_models/konko.py b/libs/community/langchain_community/chat_models/konko.py index 9fe24a50694f5..8492a5f8c56f3 100644 --- a/libs/community/langchain_community/chat_models/konko.py +++ b/libs/community/langchain_community/chat_models/konko.py @@ -3,12 +3,12 @@ import logging import os +import warnings from typing import ( Any, Dict, Iterator, List, - Mapping, Optional, Set, Tuple, @@ -19,20 +19,20 @@ from langchain_core.callbacks import ( CallbackManagerForLLMRun, ) -from langchain_core.language_models.chat_models import ( - BaseChatModel, - generate_from_stream, -) from langchain_core.messages import AIMessageChunk, BaseMessage -from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult +from langchain_core.outputs import ChatGenerationChunk, ChatResult from langchain_core.pydantic_v1 import Field, SecretStr, root_validator from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env from langchain_community.adapters.openai import ( - convert_dict_to_message, convert_message_to_dict, ) -from langchain_community.chat_models.openai import _convert_delta_to_message_chunk +from langchain_community.chat_models.openai import ( + ChatOpenAI, + _convert_delta_to_message_chunk, + generate_from_stream, +) +from langchain_community.utils.openai import is_openai_v1 DEFAULT_API_BASE = "https://api.konko.ai/v1" DEFAULT_MODEL = "meta-llama/Llama-2-13b-chat-hf" @@ -40,7 +40,7 @@ logger = logging.getLogger(__name__) -class ChatKonko(BaseChatModel): +class ChatKonko(ChatOpenAI): """`ChatKonko` Chat large language models API. To use, you should have the ``konko`` python package installed, and the @@ -72,10 +72,8 @@ def is_lc_serializable(cls) -> bool: """What sampling temperature to use.""" model_kwargs: Dict[str, Any] = Field(default_factory=dict) """Holds any model parameters valid for `create` call not explicitly specified.""" - openai_api_key: Optional[SecretStr] = None - konko_api_key: Optional[SecretStr] = None - request_timeout: Optional[Union[float, Tuple[float, float]]] = None - """Timeout for requests to Konko completion API.""" + openai_api_key: Optional[str] = None + konko_api_key: Optional[str] = None max_retries: int = 6 """Maximum number of retries to make when generating.""" streaming: bool = False @@ -100,13 +98,23 @@ def validate_environment(cls, values: Dict) -> Dict: "Please install it with `pip install konko`." ) try: - values["client"] = konko.ChatCompletion + if is_openai_v1(): + values["client"] = konko.chat.completions + else: + values["client"] = konko.ChatCompletion except AttributeError: raise ValueError( "`konko` has no `ChatCompletion` attribute, this is likely " "due to an old version of the konko package. Try upgrading it " "with `pip install --upgrade konko`." ) + + if not hasattr(konko, "_is_legacy_openai"): + warnings.warn( + "You are using an older version of the 'konko' package. " + "Please consider upgrading to access new features." + ) + if values["n"] < 1: raise ValueError("n must be at least 1.") if values["n"] > 1 and values["streaming"]: @@ -118,7 +126,6 @@ def _default_params(self) -> Dict[str, Any]: """Get the default parameters for calling Konko API.""" return { "model": self.model, - "request_timeout": self.request_timeout, "max_tokens": self.max_tokens, "stream": self.streaming, "n": self.n, @@ -182,20 +189,6 @@ def _completion_with_retry(**kwargs: Any) -> Any: return _completion_with_retry(**kwargs) - def _combine_llm_outputs(self, llm_outputs: List[Optional[dict]]) -> dict: - overall_token_usage: dict = {} - for output in llm_outputs: - if output is None: - # Happens in streaming - continue - token_usage = output["token_usage"] - for k, v in token_usage.items(): - if k in overall_token_usage: - overall_token_usage[k] += v - else: - overall_token_usage[k] = v - return {"token_usage": overall_token_usage, "model_name": self.model} - def _stream( self, messages: List[BaseMessage], @@ -259,19 +252,6 @@ def _create_message_dicts( message_dicts = [convert_message_to_dict(m) for m in messages] return message_dicts, params - def _create_chat_result(self, response: Mapping[str, Any]) -> ChatResult: - generations = [] - for res in response["choices"]: - message = convert_dict_to_message(res["message"]) - gen = ChatGeneration( - message=message, - generation_info=dict(finish_reason=res.get("finish_reason")), - ) - generations.append(gen) - token_usage = response.get("usage", {}) - llm_output = {"token_usage": token_usage, "model_name": self.model} - return ChatResult(generations=generations, llm_output=llm_output) - @property def _identifying_params(self) -> Dict[str, Any]: """Get the identifying parameters.""" diff --git a/libs/community/langchain_community/llms/__init__.py b/libs/community/langchain_community/llms/__init__.py index d13d099dc1eca..13d3607918eeb 100644 --- a/libs/community/langchain_community/llms/__init__.py +++ b/libs/community/langchain_community/llms/__init__.py @@ -270,6 +270,12 @@ def _import_koboldai() -> Any: return KoboldApiLLM +def _import_konko() -> Any: + from langchain_community.llms.konko import Konko + + return Konko + + def _import_llamacpp() -> Any: from langchain_community.llms.llamacpp import LlamaCpp @@ -639,6 +645,8 @@ def __getattr__(name: str) -> Any: return _import_javelin_ai_gateway() elif name == "KoboldApiLLM": return _import_koboldai() + elif name == "Konko": + return _import_konko() elif name == "LlamaCpp": return _import_llamacpp() elif name == "ManifestWrapper": @@ -780,6 +788,7 @@ def __getattr__(name: str) -> Any: "HuggingFaceTextGenInference", "HumanInputLLM", "KoboldApiLLM", + "Konko", "LlamaCpp", "TextGen", "ManifestWrapper", @@ -868,6 +877,7 @@ def get_type_to_cls_dict() -> Dict[str, Callable[[], Type[BaseLLM]]]: "huggingface_textgen_inference": _import_huggingface_text_gen_inference, "human-input": _import_human, "koboldai": _import_koboldai, + "konko": _import_konko, "llamacpp": _import_llamacpp, "textgen": _import_textgen, "minimax": _import_minimax, diff --git a/libs/community/langchain_community/llms/konko.py b/libs/community/langchain_community/llms/konko.py new file mode 100644 index 0000000000000..7bcd471d4e0d7 --- /dev/null +++ b/libs/community/langchain_community/llms/konko.py @@ -0,0 +1,200 @@ +"""Wrapper around Konko AI's Completion API.""" +import logging +import warnings +from typing import Any, Dict, List, Optional + +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain_core.language_models.llms import LLM +from langchain_core.pydantic_v1 import Extra, SecretStr, root_validator + +from langchain_community.utils.openai import is_openai_v1 + +logger = logging.getLogger(__name__) + + +class Konko(LLM): + """Wrapper around Konko AI models. + + To use, you'll need an API key. This can be passed in as init param + ``konko_api_key`` or set as environment variable ``KONKO_API_KEY``. + + Konko AI API reference: https://docs.konko.ai/reference/ + """ + + base_url: str = "https://api.konko.ai/v1/completions" + """Base inference API URL.""" + konko_api_key: SecretStr + """Konko AI API key.""" + model: str + """Model name. Available models listed here: + https://docs.konko.ai/reference/get_models + """ + temperature: Optional[float] = None + """Model temperature.""" + top_p: Optional[float] = None + """Used to dynamically adjust the number of choices for each predicted token based + on the cumulative probabilities. A value of 1 will always yield the same + output. A temperature less than 1 favors more correctness and is appropriate + for question answering or summarization. A value greater than 1 introduces more + randomness in the output. + """ + top_k: Optional[int] = None + """Used to limit the number of choices for the next predicted word or token. It + specifies the maximum number of tokens to consider at each step, based on their + probability of occurrence. This technique helps to speed up the generation + process and can improve the quality of the generated text by focusing on the + most likely options. + """ + max_tokens: Optional[int] = None + """The maximum number of tokens to generate.""" + repetition_penalty: Optional[float] = None + """A number that controls the diversity of generated text by reducing the + likelihood of repeated sequences. Higher values decrease repetition. + """ + logprobs: Optional[int] = None + """An integer that specifies how many top token log probabilities are included in + the response for each token generation step. + """ + + class Config: + """Configuration for this pydantic object.""" + + extra = Extra.forbid + + @root_validator(pre=True) + def validate_environment(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """Validate that python package exists in environment.""" + try: + import konko + + except ImportError: + raise ValueError( + "Could not import konko python package. " + "Please install it with `pip install konko`." + ) + if not hasattr(konko, "_is_legacy_openai"): + warnings.warn( + "You are using an older version of the 'konko' package. " + "Please consider upgrading to access new features" + "including the completion endpoint." + ) + return values + + def construct_payload( + self, + prompt: str, + stop: Optional[List[str]] = None, + **kwargs: Any, + ) -> Dict[str, Any]: + stop_to_use = stop[0] if stop and len(stop) == 1 else stop + payload: Dict[str, Any] = { + **self.default_params, + "prompt": prompt, + "stop": stop_to_use, + **kwargs, + } + return {k: v for k, v in payload.items() if v is not None} + + @property + def _llm_type(self) -> str: + """Return type of model.""" + return "konko" + + @staticmethod + def get_user_agent() -> str: + from langchain_community import __version__ + + return f"langchain/{__version__}" + + @property + def default_params(self) -> Dict[str, Any]: + return { + "model": self.model, + "temperature": self.temperature, + "top_p": self.top_p, + "top_k": self.top_k, + "max_tokens": self.max_tokens, + "repetition_penalty": self.repetition_penalty, + } + + def _call( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + """Call out to Konko's text generation endpoint. + + Args: + prompt: The prompt to pass into the model. + + Returns: + The string generated by the model.. + """ + import konko + + payload = self.construct_payload(prompt, stop, **kwargs) + + try: + if is_openai_v1(): + response = konko.completions.create(**payload) + else: + response = konko.Completion.create(**payload) + + except AttributeError: + raise ValueError( + "`konko` has no `Completion` attribute, this is likely " + "due to an old version of the konko package. Try upgrading it " + "with `pip install --upgrade konko`." + ) + + if is_openai_v1(): + output = response.choices[0].text + else: + output = response["choices"][0]["text"] + + return output + + async def _acall( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + """Asynchronously call out to Konko's text generation endpoint. + + Args: + prompt: The prompt to pass into the model. + + Returns: + The string generated by the model. + """ + import konko + + payload = self.construct_payload(prompt, stop, **kwargs) + + try: + if is_openai_v1(): + client = konko.AsyncKonko() + response = await client.completions.create(**payload) + else: + response = await konko.Completion.acreate(**payload) + + except AttributeError: + raise ValueError( + "`konko` has no `Completion` attribute, this is likely " + "due to an old version of the konko package. Try upgrading it " + "with `pip install --upgrade konko`." + ) + + if is_openai_v1(): + output = response.choices[0].text + else: + output = response["choices"][0]["text"] + + return output diff --git a/libs/community/tests/integration_tests/chat_models/test_konko.py b/libs/community/tests/integration_tests/chat_models/test_konko.py index b87e709d2088a..9f38f740eb45a 100644 --- a/libs/community/tests/integration_tests/chat_models/test_konko.py +++ b/libs/community/tests/integration_tests/chat_models/test_konko.py @@ -63,7 +63,7 @@ def test_konko_chat_test() -> None: def test_konko_chat_test_openai() -> None: """Evaluate basic ChatKonko functionality.""" - chat_instance = ChatKonko(max_tokens=10, model="gpt-3.5-turbo") + chat_instance = ChatKonko(max_tokens=10, model="meta-llama/llama-2-70b-chat") msg = HumanMessage(content="Hi") chat_response = chat_instance([msg]) assert isinstance(chat_response, BaseMessage) diff --git a/libs/community/tests/integration_tests/llms/test_konko.py b/libs/community/tests/integration_tests/llms/test_konko.py new file mode 100644 index 0000000000000..3e0fe0f31bb99 --- /dev/null +++ b/libs/community/tests/integration_tests/llms/test_konko.py @@ -0,0 +1,36 @@ +"""Test Konko API wrapper. + +In order to run this test, you need to have an Konko api key. +You'll then need to set KONKO_API_KEY environment variable to your api key. +""" +import pytest as pytest + +from langchain_community.llms import Konko + + +def test_konko_call() -> None: + """Test simple call to konko.""" + llm = Konko( + model="mistralai/mistral-7b-v0.1", + temperature=0.2, + max_tokens=250, + ) + output = llm("Say foo:") + + assert llm._llm_type == "konko" + assert isinstance(output, str) + + +async def test_konko_acall() -> None: + """Test simple call to konko.""" + llm = Konko( + model="mistralai/mistral-7b-v0.1", + temperature=0.2, + max_tokens=250, + ) + output = await llm.agenerate(["Say foo:"], stop=["bar"]) + + assert llm._llm_type == "konko" + output_text = output.generations[0][0].text + assert isinstance(output_text, str) + assert output_text.count("bar") <= 1 diff --git a/libs/community/tests/unit_tests/chat_models/konko.py b/libs/community/tests/unit_tests/chat_models/konko.py new file mode 100644 index 0000000000000..2fca6e67cb5c2 --- /dev/null +++ b/libs/community/tests/unit_tests/chat_models/konko.py @@ -0,0 +1,174 @@ +"""Evaluate ChatKonko Interface.""" +from typing import Any + +import pytest +from langchain_core.callbacks import CallbackManager +from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage +from langchain_core.outputs import ChatGeneration, ChatResult, LLMResult + +from langchain_community.chat_models.konko import ChatKonko +from tests.unit_tests.callbacks.fake_callback_handler import FakeCallbackHandler + + +def test_konko_chat_test() -> None: + """Evaluate basic ChatKonko functionality.""" + chat_instance = ChatKonko(max_tokens=10) + msg = HumanMessage(content="Hi") + chat_response = chat_instance([msg]) + assert isinstance(chat_response, BaseMessage) + assert isinstance(chat_response.content, str) + + +def test_konko_chat_test_openai() -> None: + """Evaluate basic ChatKonko functionality.""" + chat_instance = ChatKonko(max_tokens=10, model="meta-llama/llama-2-70b-chat") + msg = HumanMessage(content="Hi") + chat_response = chat_instance([msg]) + assert isinstance(chat_response, BaseMessage) + assert isinstance(chat_response.content, str) + + +def test_konko_model_test() -> None: + """Check how ChatKonko manages model_name.""" + chat_instance = ChatKonko(model="alpha") + assert chat_instance.model == "alpha" + chat_instance = ChatKonko(model="beta") + assert chat_instance.model == "beta" + + +def test_konko_available_model_test() -> None: + """Check how ChatKonko manages model_name.""" + chat_instance = ChatKonko(max_tokens=10, n=2) + res = chat_instance.get_available_models() + assert isinstance(res, set) + + +def test_konko_system_msg_test() -> None: + """Evaluate ChatKonko's handling of system messages.""" + chat_instance = ChatKonko(max_tokens=10) + sys_msg = SystemMessage(content="Initiate user chat.") + user_msg = HumanMessage(content="Hi there") + chat_response = chat_instance([sys_msg, user_msg]) + assert isinstance(chat_response, BaseMessage) + assert isinstance(chat_response.content, str) + + +def test_konko_generation_test() -> None: + """Check ChatKonko's generation ability.""" + chat_instance = ChatKonko(max_tokens=10, n=2) + msg = HumanMessage(content="Hi") + gen_response = chat_instance.generate([[msg], [msg]]) + assert isinstance(gen_response, LLMResult) + assert len(gen_response.generations) == 2 + for gen_list in gen_response.generations: + assert len(gen_list) == 2 + for gen in gen_list: + assert isinstance(gen, ChatGeneration) + assert isinstance(gen.text, str) + assert gen.text == gen.message.content + + +def test_konko_multiple_outputs_test() -> None: + """Test multiple completions with ChatKonko.""" + chat_instance = ChatKonko(max_tokens=10, n=5) + msg = HumanMessage(content="Hi") + gen_response = chat_instance._generate([msg]) + assert isinstance(gen_response, ChatResult) + assert len(gen_response.generations) == 5 + for gen in gen_response.generations: + assert isinstance(gen.message, BaseMessage) + assert isinstance(gen.message.content, str) + + +def test_konko_streaming_callback_test() -> None: + """Evaluate streaming's token callback functionality.""" + callback_instance = FakeCallbackHandler() + callback_mgr = CallbackManager([callback_instance]) + chat_instance = ChatKonko( + max_tokens=10, + streaming=True, + temperature=0, + callback_manager=callback_mgr, + verbose=True, + ) + msg = HumanMessage(content="Hi") + chat_response = chat_instance([msg]) + assert callback_instance.llm_streams > 0 + assert isinstance(chat_response, BaseMessage) + + +def test_konko_streaming_info_test() -> None: + """Ensure generation details are retained during streaming.""" + + class TestCallback(FakeCallbackHandler): + data_store: dict = {} + + def on_llm_end(self, *args: Any, **kwargs: Any) -> Any: + self.data_store["generation"] = args[0] + + callback_instance = TestCallback() + callback_mgr = CallbackManager([callback_instance]) + chat_instance = ChatKonko( + max_tokens=2, + temperature=0, + callback_manager=callback_mgr, + ) + list(chat_instance.stream("hey")) + gen_data = callback_instance.data_store["generation"] + assert gen_data.generations[0][0].text == " Hey" + + +def test_konko_llm_model_name_test() -> None: + """Check if llm_output has model info.""" + chat_instance = ChatKonko(max_tokens=10) + msg = HumanMessage(content="Hi") + llm_data = chat_instance.generate([[msg]]) + assert llm_data.llm_output is not None + assert llm_data.llm_output["model_name"] == chat_instance.model + + +def test_konko_streaming_model_name_test() -> None: + """Check model info during streaming.""" + chat_instance = ChatKonko(max_tokens=10, streaming=True) + msg = HumanMessage(content="Hi") + llm_data = chat_instance.generate([[msg]]) + assert llm_data.llm_output is not None + assert llm_data.llm_output["model_name"] == chat_instance.model + + +def test_konko_streaming_param_validation_test() -> None: + """Ensure correct token callback during streaming.""" + with pytest.raises(ValueError): + ChatKonko( + max_tokens=10, + streaming=True, + temperature=0, + n=5, + ) + + +def test_konko_additional_args_test() -> None: + """Evaluate extra arguments for ChatKonko.""" + chat_instance = ChatKonko(extra=3, max_tokens=10) + assert chat_instance.max_tokens == 10 + assert chat_instance.model_kwargs == {"extra": 3} + + chat_instance = ChatKonko(extra=3, model_kwargs={"addition": 2}) + assert chat_instance.model_kwargs == {"extra": 3, "addition": 2} + + with pytest.raises(ValueError): + ChatKonko(extra=3, model_kwargs={"extra": 2}) + + with pytest.raises(ValueError): + ChatKonko(model_kwargs={"temperature": 0.2}) + + with pytest.raises(ValueError): + ChatKonko(model_kwargs={"model": "text-davinci-003"}) + + +def test_konko_token_streaming_test() -> None: + """Check token streaming for ChatKonko.""" + chat_instance = ChatKonko(max_tokens=10) + + for token in chat_instance.stream("Just a test"): + assert isinstance(token.content, str) diff --git a/libs/community/tests/unit_tests/llms/konko.py b/libs/community/tests/unit_tests/llms/konko.py new file mode 100644 index 0000000000000..3e0fe0f31bb99 --- /dev/null +++ b/libs/community/tests/unit_tests/llms/konko.py @@ -0,0 +1,36 @@ +"""Test Konko API wrapper. + +In order to run this test, you need to have an Konko api key. +You'll then need to set KONKO_API_KEY environment variable to your api key. +""" +import pytest as pytest + +from langchain_community.llms import Konko + + +def test_konko_call() -> None: + """Test simple call to konko.""" + llm = Konko( + model="mistralai/mistral-7b-v0.1", + temperature=0.2, + max_tokens=250, + ) + output = llm("Say foo:") + + assert llm._llm_type == "konko" + assert isinstance(output, str) + + +async def test_konko_acall() -> None: + """Test simple call to konko.""" + llm = Konko( + model="mistralai/mistral-7b-v0.1", + temperature=0.2, + max_tokens=250, + ) + output = await llm.agenerate(["Say foo:"], stop=["bar"]) + + assert llm._llm_type == "konko" + output_text = output.generations[0][0].text + assert isinstance(output_text, str) + assert output_text.count("bar") <= 1 diff --git a/libs/community/tests/unit_tests/llms/test_imports.py b/libs/community/tests/unit_tests/llms/test_imports.py index 7bb66ff341b8d..2e5fed3a70c94 100644 --- a/libs/community/tests/unit_tests/llms/test_imports.py +++ b/libs/community/tests/unit_tests/llms/test_imports.py @@ -41,6 +41,7 @@ "HuggingFaceTextGenInference", "HumanInputLLM", "KoboldApiLLM", + "Konko", "LlamaCpp", "TextGen", "ManifestWrapper", From 4ec3fe46806701a73caa9c49b3c06da4819ceb85 Mon Sep 17 00:00:00 2001 From: JongRok BAEK <54343137+L-cloud@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:36:28 +0900 Subject: [PATCH 201/215] docs: Updated integration docs structure for chat/anthropic (#16268) Description: - Added output and environment variables - Updated the documentation for chat/anthropic, changing references from `langchain.schema` to `langchain_core.prompts`. Issue: https://github.com/langchain-ai/langchain/issues/15664 Dependencies: None Twitter handle: None Since this is my first open-source PR, please feel free to point out any mistakes, and I'll be eager to make corrections. --- docs/docs/integrations/chat/anthropic.ipynb | 269 +++++++++++++++++--- 1 file changed, 229 insertions(+), 40 deletions(-) diff --git a/docs/docs/integrations/chat/anthropic.ipynb b/docs/docs/integrations/chat/anthropic.ipynb index b7c5fbce30484..f8aa35142de11 100644 --- a/docs/docs/integrations/chat/anthropic.ipynb +++ b/docs/docs/integrations/chat/anthropic.ipynb @@ -22,44 +22,84 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "d4a7c55d-b235-4ca4-a579-c90cc9570da9", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:00.590587Z", + "start_time": "2024-01-19T11:25:00.127293Z" + }, "tags": [] }, "outputs": [], "source": [ - "from langchain.schema import HumanMessage\n", - "from langchain_community.chat_models import ChatAnthropic" + "from langchain_community.chat_models import ChatAnthropic\n", + "from langchain_core.prompts import ChatPromptTemplate" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "70cf04e8-423a-4ff6-8b09-f11fb711c817", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:04.349676Z", + "start_time": "2024-01-19T11:25:03.964930Z" + }, "tags": [] }, "outputs": [], "source": [ - "chat = ChatAnthropic()" + "chat = ChatAnthropic(temperature=0, model_name=\"claude-2\")" + ] + }, + { + "cell_type": "markdown", + "id": "d1f9df276476f0bc", + "metadata": { + "collapsed": false + }, + "source": [ + "The code provided assumes that your ANTHROPIC_API_KEY is set in your environment variables. If you would like to manually specify your API key and also choose a different model, you can use the following code:\n", + "```python\n", + "chat = ChatAnthropic(temperature=0, anthropic_api_key=\"YOUR_API_KEY\", model_name=\"claude-instant-1.2\")\n", + "\n", + "```\n", + "Please note that the default model is \"claude-2,\" and you can check the available models at [here](https://docs.anthropic.com/claude/reference/selecting-a-model)." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "8199ef8f-eb8b-4253-9ea0-6c24a013ca4c", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:07.274418Z", + "start_time": "2024-01-19T11:25:05.898031Z" + }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": "AIMessage(content=' 저는 파이썬을 좋아합니다.')" + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "messages = [\n", - " HumanMessage(\n", - " content=\"Translate this sentence from English to French. I love programming.\"\n", - " )\n", - "]\n", - "chat.invoke(messages)" + "system = \"You are a helpful assistant that translates {input_language} to {output_language}.\"\n", + "human = \"{text}\"\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n", + "\n", + "chain = prompt | chat\n", + "chain.invoke({\n", + " \"input_language\": \"English\",\n", + " \"output_language\": \"Korean\",\n", + " \"text\": \"I love Python\",\n", + "})" ] }, { @@ -72,44 +112,78 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "93a21c5c-6ef9-4688-be60-b2e1f94842fb", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "from langchain.callbacks.manager import CallbackManager\n", - "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "c5fac0e9-05a4-4fc1-a3b3-e5bbb24b971b", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:10.448733Z", + "start_time": "2024-01-19T11:25:08.866277Z" + }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": "AIMessage(content=\" Why don't bears like fast food? Because they can't catch it!\")" + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "await chat.ainvoke([messages])" + "chat = ChatAnthropic(temperature=0, model_name=\"claude-2\")\n", + "prompt = ChatPromptTemplate.from_messages([(\"human\", \"Tell me a joke about {topic}\")])\n", + "chain = prompt | chat\n", + "await chain.ainvoke({\"topic\": \"bear\"})" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "025be980-e50d-4a68-93dc-c9c7b500ce34", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:24.438696Z", + "start_time": "2024-01-19T11:25:14.687480Z" + }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Here are some of the most famous tourist attractions in Japan:\n", + "\n", + "- Tokyo - Tokyo Tower, Tokyo Skytree, Imperial Palace, Sensoji Temple, Meiji Shrine, Shibuya Crossing\n", + "\n", + "- Kyoto - Kinkakuji (Golden Pavilion), Fushimi Inari Shrine, Kiyomizu-dera Temple, Arashiyama Bamboo Grove, Gion Geisha District\n", + "\n", + "- Osaka - Osaka Castle, Dotonbori, Universal Studios Japan, Osaka Aquarium Kaiyukan \n", + "\n", + "- Hiroshima - Hiroshima Peace Memorial Park and Museum, Itsukushima Shrine (Miyajima Island)\n", + "\n", + "- Mount Fuji - Iconic and famous mountain, popular for hiking and viewing from places like Hakone and Kawaguchiko Lake\n", + "\n", + "- Himeji - Himeji Castle, one of Japan's most impressive feudal castles\n", + "\n", + "- Nara - Todaiji Temple, Nara Park with its bowing deer, Horyuji Temple with some of world's oldest wooden structures \n", + "\n", + "- Nikko - Elaborate shrines and temples nestled around Nikko National Park\n", + "\n", + "- Sapporo - Snow" + ] + } + ], "source": [ - "chat = ChatAnthropic(\n", - " streaming=True,\n", - " verbose=True,\n", - " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),\n", + "chat = ChatAnthropic(temperature=0.3, model_name=\"claude-2\")\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"human\", \"Give me a list of famous tourist attractions in Japan\")]\n", ")\n", - "chat.stream(messages)" + "chain = prompt | chat\n", + "for chunk in chain.stream({}):\n", + " print(chunk.content, end=\"\", flush=True)" ] }, { @@ -134,15 +208,130 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "07c47c2a", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:25.288133Z", + "start_time": "2024-01-19T11:25:24.438968Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": "AIMessage(content='파이썬을 사랑합니다.')" + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from langchain_anthropic import ChatAnthropicMessages\n", "\n", "chat = ChatAnthropicMessages(model_name=\"claude-instant-1.2\")\n", - "chat.invoke(messages)" + "system = (\n", + " \"You are a helpful assistant that translates {input_language} to {output_language}.\"\n", + ")\n", + "human = \"{text}\"\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n", + "\n", + "chain = prompt | chat\n", + "chain.invoke(\n", + " {\n", + " \"input_language\": \"English\",\n", + " \"output_language\": \"Korean\",\n", + " \"text\": \"I love Python\",\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "19e53d75935143fd", + "metadata": { + "collapsed": false + }, + "source": [ + "ChatAnthropicMessages also requires the anthropic_api_key argument, or the ANTHROPIC_API_KEY environment variable must be set. \n", + "\n", + "ChatAnthropicMessages also supports async and streaming functionality:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e20a139d30e3d333", + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:26.012325Z", + "start_time": "2024-01-19T11:25:25.288358Z" + }, + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": "AIMessage(content='파이썬을 사랑합니다.')" + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await chain.ainvoke(\n", + " {\n", + " \"input_language\": \"English\",\n", + " \"output_language\": \"Korean\",\n", + " \"text\": \"I love Python\",\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6f34f1073d7e7120", + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-19T11:25:28.323455Z", + "start_time": "2024-01-19T11:25:26.012040Z" + }, + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Here are some of the most famous tourist attractions in Japan:\n", + "\n", + "- Tokyo Tower - A communication and observation tower in Tokyo modeled after the Eiffel Tower. It offers stunning views of the city.\n", + "\n", + "- Mount Fuji - Japan's highest and most famous mountain. It's a iconic symbol of Japan and a UNESCO World Heritage Site. \n", + "\n", + "- Itsukushima Shrine (Miyajima) - A shrine located on an island in Hiroshima prefecture, known for its \"floating\" torii gate that seems to float on water during high tide.\n", + "\n", + "- Himeji Castle - A UNESCO World Heritage Site famous for having withstood numerous battles without destruction to its intricate white walls and sloping, triangular roofs. \n", + "\n", + "- Kawaguchiko Station - Near Mount Fuji, this area is known for its scenic Fuji Five Lakes region. \n", + "\n", + "- Hiroshima Peace Memorial Park and Museum - Commemorates the world's first atomic bombing in Hiroshima on August 6, 1945. \n", + "\n", + "- Arashiyama Bamboo Grove - A renowned bamboo forest located in Kyoto that draws many visitors.\n", + "\n", + "- Kegon Falls - One of Japan's largest waterfalls" + ] + } + ], + "source": [ + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"human\", \"Give me a list of famous tourist attractions in Japan\")]\n", + ")\n", + "chain = prompt | chat\n", + "for chunk in chain.stream({}):\n", + " print(chunk.content, end=\"\", flush=True)" ] } ], From ff3163297bcd5da3f783d3cd700d16f1d41c2c6b Mon Sep 17 00:00:00 2001 From: bu2kx <144132509+bu2kx@users.noreply.github.com> Date: Wed, 24 Jan 2024 03:37:01 +0100 Subject: [PATCH 202/215] community[minor]: Add KDBAI vector store (#12797) Addition of KDBAI vector store (https://kdb.ai). Dependencies: `kdbai_client` v0.1.2 Python package. Sample notebook: `docs/docs/integrations/vectorstores/kdbai.ipynb` Tag maintainer: @bu2kx Twitter handle: @kxsystems --- docs/docs/integrations/providers/kdbai.mdx | 24 + .../integrations/vectorstores/kdbai.ipynb | 510 ++++++++++++++++++ .../vectorstores/__init__.py | 9 + .../langchain_community/vectorstores/kdbai.py | 267 +++++++++ .../vectorstores/test_public_api.py | 1 + 5 files changed, 811 insertions(+) create mode 100644 docs/docs/integrations/providers/kdbai.mdx create mode 100644 docs/docs/integrations/vectorstores/kdbai.ipynb create mode 100644 libs/community/langchain_community/vectorstores/kdbai.py diff --git a/docs/docs/integrations/providers/kdbai.mdx b/docs/docs/integrations/providers/kdbai.mdx new file mode 100644 index 0000000000000..a5f06d0128748 --- /dev/null +++ b/docs/docs/integrations/providers/kdbai.mdx @@ -0,0 +1,24 @@ +# KDB.AI + +>[KDB.AI](https://kdb.ai) is a powerful knowledge-based vector database and search engine that allows you to build scalable, reliable AI applications, using real-time data, by providing advanced search, recommendation and personalization. + + +## Installation and Setup + +Install the Python SDK: + +```bash +pip install kdbai-client +``` + + +## Vector store + +There exists a wrapper around KDB.AI indexes, allowing you to use it as a vectorstore, +whether for semantic search or example selection. + +```python +from langchain_community.vectorstores import KDBAI +``` + +For a more detailed walkthrough of the KDB.AI vectorstore, see [this notebook](/docs/integrations/vectorstores/kdbai) diff --git a/docs/docs/integrations/vectorstores/kdbai.ipynb b/docs/docs/integrations/vectorstores/kdbai.ipynb new file mode 100644 index 0000000000000..39c1f1fdec300 --- /dev/null +++ b/docs/docs/integrations/vectorstores/kdbai.ipynb @@ -0,0 +1,510 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "08b3f3a3-7542-4d39-a9a1-f66e50ec3c0f", + "metadata": {}, + "source": [ + "# KDB.AI\n", + "\n", + "> [KDB.AI](https://kdb.ai/) is a powerful knowledge-based vector database and search engine that allows you to build scalable, reliable AI applications, using real-time data, by providing advanced search, recommendation and personalization.\n", + "\n", + "[This example](https://github.com/KxSystems/kdbai-samples/blob/main/document_search/document_search.ipynb) demonstrates how to use KDB.AI to run semantic search on unstructured text documents.\n", + "\n", + "To access your end point and API keys, [sign up to KDB.AI here](https://kdb.ai/get-started/).\n", + "\n", + "To set up your development environment, follow the instructions on the [KDB.AI pre-requisites page](https://code.kx.com/kdbai/pre-requisites.html).\n", + "\n", + "The following examples demonstrate some of the ways you can interact with KDB.AI through LangChain.\n", + "\n", + "## Import required packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2704194d-c42d-463d-b162-fb95262e052c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "from getpass import getpass\n", + "\n", + "import kdbai_client as kdbai\n", + "import pandas as pd\n", + "import requests\n", + "from langchain.chains import RetrievalQA\n", + "from langchain.document_loaders import PyPDFLoader\n", + "from langchain_community.vectorstores import KDBAI\n", + "from langchain_openai import ChatOpenAI, OpenAIEmbeddings" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "04848fcf-e128-4d63-af6c-b3991531d62e", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "KDB.AI endpoint: https://ui.qa.cld.kx.com/instance/pcnvlmi860\n", + "KDB.AI API key: ········\n", + "OpenAI API Key: ········\n" + ] + } + ], + "source": [ + "KDBAI_ENDPOINT = input(\"KDB.AI endpoint: \")\n", + "KDBAI_API_KEY = getpass(\"KDB.AI API key: \")\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass(\"OpenAI API Key: \")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d08a1468-6bff-4a65-8b4a-9835cfa997ad", + "metadata": {}, + "outputs": [], + "source": [ + "TEMP = 0.0\n", + "K = 3" + ] + }, + { + "cell_type": "markdown", + "id": "63a111d8-2422-4d33-85c0-bc95d25e330a", + "metadata": {}, + "source": [ + "## Create a KBD.AI Session" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9ffe4fee-2dc3-4943-917b-28adc3a69472", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Create a KDB.AI session...\n" + ] + } + ], + "source": [ + "print(\"Create a KDB.AI session...\")\n", + "session = kdbai.Session(endpoint=KDBAI_ENDPOINT, api_key=KDBAI_API_KEY)" + ] + }, + { + "cell_type": "markdown", + "id": "a2ea7e87-f65c-43d9-bc67-be7bda86def2", + "metadata": {}, + "source": [ + "## Create a table" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "da27f31c-890e-46c0-8e01-1b8474ee3a70", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Create table \"documents\"...\n" + ] + } + ], + "source": [ + "print('Create table \"documents\"...')\n", + "schema = {\n", + " \"columns\": [\n", + " {\"name\": \"id\", \"pytype\": \"str\"},\n", + " {\"name\": \"text\", \"pytype\": \"bytes\"},\n", + " {\n", + " \"name\": \"embeddings\",\n", + " \"pytype\": \"float32\",\n", + " \"vectorIndex\": {\"dims\": 1536, \"metric\": \"L2\", \"type\": \"hnsw\"},\n", + " },\n", + " {\"name\": \"tag\", \"pytype\": \"str\"},\n", + " {\"name\": \"title\", \"pytype\": \"bytes\"},\n", + " ]\n", + "}\n", + "table = session.create_table(\"documents\", schema)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "930ba64a-1cf9-4892-9335-8745c830497c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 44.1 ms, sys: 6.04 ms, total: 50.2 ms\n", + "Wall time: 213 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "562978" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "URL = 'https://www.conseil-constitutionnel.fr/node/3850/pdf'\n", + "PDF = 'Déclaration_des_droits_de_l_homme_et_du_citoyen.pdf'\n", + "open(PDF, 'wb').write(requests.get(URL).content)" + ] + }, + { + "cell_type": "markdown", + "id": "0f7da153-e7d4-4a4c-b044-ad7b4d893c7f", + "metadata": {}, + "source": [ + "## Read a PDF" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "00873e6b-f204-4dca-b82b-1c45d0b83ee5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Read a PDF...\n", + "CPU times: user 156 ms, sys: 12.5 ms, total: 169 ms\n", + "Wall time: 183 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "print('Read a PDF...')\n", + "loader = PyPDFLoader(PDF)\n", + "pages = loader.load_and_split()\n", + "len(pages)" + ] + }, + { + "cell_type": "markdown", + "id": "3536c7db-0db7-446a-b61e-149fd3c2d1d8", + "metadata": {}, + "source": [ + "## Create a Vector Database from PDF Text" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b06d4a96-c3d5-426b-9e22-12925b14e5e6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Create a Vector Database from PDF text...\n", + "CPU times: user 211 ms, sys: 18.4 ms, total: 229 ms\n", + "Wall time: 2.23 s\n" + ] + }, + { + "data": { + "text/plain": [ + "['3ef27d23-47cf-419b-8fe9-5dfae9e8e895',\n", + " 'd3a9a69d-28f5-434b-b95b-135db46695c8',\n", + " 'd2069bda-c0b8-4791-b84d-0c6f84f4be34']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "print('Create a Vector Database from PDF text...')\n", + "embeddings = OpenAIEmbeddings(model='text-embedding-ada-002')\n", + "texts = [p.page_content for p in pages]\n", + "metadata = pd.DataFrame(index=list(range(len(texts))))\n", + "metadata['tag'] = 'law'\n", + "metadata['title'] = 'Déclaration des Droits de l\\'Homme et du Citoyen de 1789'.encode('utf-8')\n", + "vectordb = KDBAI(table, embeddings)\n", + "vectordb.add_texts(texts=texts, metadatas=metadata)" + ] + }, + { + "cell_type": "markdown", + "id": "3b658f9a-61dd-4a88-9bcb-4651992f610d", + "metadata": {}, + "source": [ + "## Create LangChain Pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6d848577-1192-4bb0-b721-37f52be5d9d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Create LangChain Pipeline...\n", + "CPU times: user 40.8 ms, sys: 4.69 ms, total: 45.5 ms\n", + "Wall time: 44.7 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "print('Create LangChain Pipeline...')\n", + "qabot = RetrievalQA.from_chain_type(chain_type='stuff',\n", + " llm=ChatOpenAI(model='gpt-3.5-turbo-16k', temperature=TEMP), \n", + " retriever=vectordb.as_retriever(search_kwargs=dict(k=K)),\n", + " return_source_documents=True)" + ] + }, + { + "cell_type": "markdown", + "id": "21113a5e-d72d-4a44-9714-6b23ec95b755", + "metadata": {}, + "source": [ + "## Summarize the document in English" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "81668f8f-a416-4b58-93d2-8e0924ceca23", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Summarize the document in English:\n", + "\n", + "The document is the Declaration of the Rights of Man and of the Citizen of 1789. It was written by the representatives of the French people and aims to declare the natural, inalienable, and sacred rights of every individual. These rights include freedom, property, security, and resistance to oppression. The document emphasizes the importance of equality and the principle that sovereignty resides in the nation. It also highlights the role of law in protecting individual rights and ensuring the common good. The document asserts the right to freedom of thought, expression, and religion, as long as it does not disturb public order. It emphasizes the need for a public force to guarantee the rights of all citizens and the importance of a fair and equal distribution of public contributions. The document also recognizes the right of citizens to hold public officials accountable and states that any society without the guarantee of rights and separation of powers does not have a constitution. Finally, it affirms the inviolable and sacred nature of property, stating that it can only be taken away for public necessity and with just compensation.\n", + "CPU times: user 144 ms, sys: 50.2 ms, total: 194 ms\n", + "Wall time: 4.96 s\n" + ] + } + ], + "source": [ + "%%time\n", + "Q = 'Summarize the document in English:'\n", + "print(f'\\n\\n{Q}\\n')\n", + "print(qabot.invoke(dict(query=Q))['result'])" + ] + }, + { + "cell_type": "markdown", + "id": "9ce7667e-8c89-466c-8040-9ba62f3e57ec", + "metadata": {}, + "source": [ + "## Query the Data" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e02a7acb-99ac-48f8-b93c-d95a8f9e87d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Is it a fair law and why ?\n", + "\n", + "As an AI language model, I don't have personal opinions. However, I can provide some analysis based on the given context. The text provided is an excerpt from the Declaration of the Rights of Man and of the Citizen of 1789, which is considered a foundational document in the history of human rights. It outlines the natural and inalienable rights of individuals, such as freedom, property, security, and resistance to oppression. It also emphasizes the principles of equality, the rule of law, and the separation of powers. \n", + "\n", + "Whether or not this law is considered fair is subjective and can vary depending on individual perspectives and societal norms. However, many consider the principles and rights outlined in this declaration to be fundamental and just. It is important to note that this declaration was a significant step towards establishing principles of equality and individual rights in France and has influenced subsequent human rights documents worldwide.\n", + "CPU times: user 85.1 ms, sys: 5.93 ms, total: 91.1 ms\n", + "Wall time: 5.11 s\n" + ] + } + ], + "source": [ + "%%time\n", + "Q = 'Is it a fair law and why ?'\n", + "print(f'\\n\\n{Q}\\n')\n", + "print(qabot.invoke(dict(query=Q))['result'])" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "24dc85bd-cd35-4fb3-9d01-e00a896fd9a1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "What are the rights and duties of the man, the citizen and the society ?\n", + "\n", + "According to the Declaration of the Rights of Man and of the Citizen of 1789, the rights and duties of man, citizen, and society are as follows:\n", + "\n", + "Rights of Man:\n", + "1. Men are born and remain free and equal in rights. Social distinctions can only be based on common utility.\n", + "2. The purpose of political association is the preservation of the natural and imprescriptible rights of man, which are liberty, property, security, and resistance to oppression.\n", + "3. The principle of sovereignty resides essentially in the nation. No body or individual can exercise any authority that does not emanate expressly from it.\n", + "4. Liberty consists of being able to do anything that does not harm others. The exercise of natural rights of each man has no limits other than those that ensure the enjoyment of these same rights by other members of society. These limits can only be determined by law.\n", + "5. The law has the right to prohibit only actions harmful to society. Anything not prohibited by law cannot be prevented, and no one can be compelled to do what it does not command.\n", + "6. The law is the expression of the general will. All citizens have the right to participate personally, or through their representatives, in its formation. It must be the same for all, whether it protects or punishes. All citizens, being equal in its eyes, are equally eligible to all public dignities, places, and employments, according to their abilities, and without other distinction than that of their virtues and talents.\n", + "7. No man can be accused, arrested, or detained except in cases determined by law and according to the forms it has prescribed. Those who solicit, expedite, execute, or cause to be executed arbitrary orders must be punished. But any citizen called or seized in virtue of the law must obey instantly; he renders himself culpable by resistance.\n", + "8. The law should establish only strictly and evidently necessary penalties, and no one can be punished except in virtue of a law established and promulgated prior to the offense, and legally applied.\n", + "9. Every man being presumed innocent until he has been declared guilty, if it is judged indispensable to arrest him, any rigor that is not necessary to secure his person must be severely repressed by the law.\n", + "10. No one should be disturbed for his opinions, even religious ones, as long as their manifestation does not disturb the established public order by law.\n", + "11. The free communication of ideas and opinions is one of the most precious rights of man. Every citizen may therefore speak, write, and print freely, except to respond to the abuse of this liberty in cases determined by law.\n", + "12. The guarantee of the rights of man and of the citizen requires a public force. This force is therefore instituted for the advantage of all and not for the particular utility of those to whom it is entrusted.\n", + "13. For the maintenance of the public force and for the expenses of administration, a common contribution is necessary. It must be equally distributed among all citizens, in proportion to their abilities.\n", + "14. All citizens have the right to ascertain, by themselves or through their representatives, the necessity of the public contribution, to consent to it freely, to follow its use, and to determine its amount, basis, collection, and duration.\n", + "15. Society has the right to ask any public agent for an account of his administration.\n", + "16. Any society in which the guarantee of rights is not assured, nor the separation of powers determined, has no constitution.\n", + "17. Property being an inviolable and sacred right, no one can be deprived of it, except when public necessity, legally determined, obviously requires it, and under the condition of a just and prior indemnity.\n", + "\n", + "Duties of the Citizen:\n", + "The Declaration does not explicitly mention the duties of the citizen, but it emphasizes the importance of the law and the need for citizens to obey it. It also highlights the responsibility of citizens to participate in the formation of the law and to hold public agents accountable for their administration.\n", + "\n", + "Duties of Society:\n", + "The Declaration does not explicitly mention the duties of society, but it emphasizes the importance of guaranteeing the rights of man and of the citizen. Society is responsible for ensuring the preservation of these rights and the separation of powers, as well as providing a public force for the advantage of all.\n", + "CPU times: user 86.5 ms, sys: 5.45 ms, total: 92 ms\n", + "Wall time: 14.9 s\n" + ] + } + ], + "source": [ + "%%time\n", + "Q = 'What are the rights and duties of the man, the citizen and the society ?'\n", + "print(f'\\n\\n{Q}\\n')\n", + "print(qabot.invoke(dict(query=Q))['result'])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "d55b7980-a3d3-45d7-a310-99492edb098b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Is this law practical ?\n", + "\n", + "As an AI language model, I cannot provide personal opinions or subjective judgments on whether a law is practical or not. The texts provided are excerpts from the French Constitution and the Declaration of the Rights of Man and of the Citizen of 1789. These texts outline fundamental rights and principles that form the basis of the French legal system. The practicality of a law is often a matter of interpretation and can vary depending on the context and specific circumstances. It is ultimately up to legal experts, lawmakers, and the judiciary to determine the practicality and application of these laws in specific cases.\n", + "CPU times: user 91.4 ms, sys: 5.89 ms, total: 97.3 ms\n", + "Wall time: 2.78 s\n" + ] + } + ], + "source": [ + "%%time\n", + "Q = 'Is this law practical ?'\n", + "print(f'\\n\\n{Q}\\n')\n", + "print(qabot.invoke(dict(query=Q))['result'])" + ] + }, + { + "cell_type": "markdown", + "id": "5f9d0a3c-4941-4f65-b6b8-aefe4f6abd14", + "metadata": {}, + "source": [ + "## Clean up the Documents table" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cdddda29-e28d-423f-b1c6-f77d39acc3dd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Clean up KDB.AI \"documents\" table and index for similarity search\n", + "# so this notebook could be played again and again\n", + "session.table(\"documents\").drop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23cb1359-f32c-4b47-a885-cbf3cbae5b14", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/community/langchain_community/vectorstores/__init__.py b/libs/community/langchain_community/vectorstores/__init__.py index 1cb42b8fc5131..a5fe62dd99dd3 100644 --- a/libs/community/langchain_community/vectorstores/__init__.py +++ b/libs/community/langchain_community/vectorstores/__init__.py @@ -210,6 +210,12 @@ def _import_hologres() -> Any: return Hologres +def _import_kdbai() -> Any: + from langchain_community.vectorstores.kdbai import KDBAI + + return KDBAI + + def _import_lancedb() -> Any: from langchain_community.vectorstores.lancedb import LanceDB @@ -523,6 +529,8 @@ def __getattr__(name: str) -> Any: return _import_faiss() elif name == "Hologres": return _import_hologres() + elif name == "KDBAI": + return _import_kdbai() elif name == "LanceDB": return _import_lancedb() elif name == "LLMRails": @@ -638,6 +646,7 @@ def __getattr__(name: str) -> Any: "Epsilla", "FAISS", "Hologres", + "KDBAI", "LanceDB", "LLMRails", "Marqo", diff --git a/libs/community/langchain_community/vectorstores/kdbai.py b/libs/community/langchain_community/vectorstores/kdbai.py new file mode 100644 index 0000000000000..1122b691d439b --- /dev/null +++ b/libs/community/langchain_community/vectorstores/kdbai.py @@ -0,0 +1,267 @@ +from __future__ import annotations + +import logging +import uuid +from typing import Any, Iterable, List, Optional, Tuple + +from langchain_core.documents import Document +from langchain_core.embeddings import Embeddings +from langchain_core.vectorstores import VectorStore + +from langchain_community.vectorstores.utils import DistanceStrategy + +logger = logging.getLogger(__name__) + + +class KDBAI(VectorStore): + """`KDB.AI` vector store [https://kdb.ai](https://kdb.ai) + + To use, you should have the `kdbai_client` python package installed. + + Args: + table: kdbai_client.Table object to use as storage, + embedding: Any embedding function implementing + `langchain.embeddings.base.Embeddings` interface, + distance_strategy: One option from DistanceStrategy.EUCLIDEAN_DISTANCE, + DistanceStrategy.DOT_PRODUCT or DistanceStrategy.COSINE. + + See the example [notebook](https://github.com/KxSystems/langchain/blob/KDB.AI/docs/docs/integrations/vectorstores/kdbai.ipynb). + """ + + def __init__( + self, + table: Any, + embedding: Embeddings, + distance_strategy: Optional[ + DistanceStrategy + ] = DistanceStrategy.EUCLIDEAN_DISTANCE, + ): + try: + import kdbai_client # noqa + except ImportError: + raise ImportError( + "Could not import kdbai_client python package. " + "Please install it with `pip install kdbai_client`." + ) + self._table = table + self._embedding = embedding + self.distance_strategy = distance_strategy + + @property + def embeddings(self) -> Optional[Embeddings]: + if isinstance(self._embedding, Embeddings): + return self._embedding + return None + + def _embed_documents(self, texts: Iterable[str]) -> List[List[float]]: + if isinstance(self._embedding, Embeddings): + return self._embedding.embed_documents(list(texts)) + return [self._embedding(t) for t in texts] + + def _embed_query(self, text: str) -> List[float]: + if isinstance(self._embedding, Embeddings): + return self._embedding.embed_query(text) + return self._embedding(text) + + def _insert( + self, + texts: List[str], + ids: Optional[List[str]], + metadata: Optional[Any] = None, + ) -> None: + try: + import numpy as np + except ImportError: + raise ImportError( + "Could not import numpy python package. " + "Please install it with `pip install numpy`." + ) + + try: + import pandas as pd + except ImportError: + raise ImportError( + "Could not import pandas python package. " + "Please install it with `pip install pandas`." + ) + + embeds = self._embedding.embed_documents(texts) + df = pd.DataFrame() + df["id"] = ids + df["text"] = [t.encode("utf-8") for t in texts] + df["embeddings"] = [np.array(e, dtype="float32") for e in embeds] + if metadata is not None: + df = pd.concat([df, metadata], axis=1) + self._table.insert(df, warn=False) + + def add_texts( + self, + texts: Iterable[str], + metadatas: Optional[List[dict]] = None, + ids: Optional[List[str]] = None, + batch_size: int = 32, + **kwargs: Any, + ) -> List[str]: + """Run more texts through the embeddings and add to the vectorstore. + + Args: + texts (Iterable[str]): Texts to add to the vectorstore. + metadatas (Optional[List[dict]]): List of metadata corresponding to each + chunk of text. + ids (Optional[List[str]]): List of IDs corresponding to each chunk of text. + batch_size (Optional[int]): Size of batch of chunks of text to insert at + once. + + Returns: + List[str]: List of IDs of the added texts. + """ + + try: + import pandas as pd + except ImportError: + raise ImportError( + "Could not import pandas python package. " + "Please install it with `pip install pandas`." + ) + + texts = list(texts) + metadf: pd.DataFrame = None + if metadatas is not None: + if isinstance(metadatas, pd.DataFrame): + metadf = metadatas + else: + metadf = pd.DataFrame(metadatas) + out_ids: List[str] = [] + nbatches = (len(texts) - 1) // batch_size + 1 + for i in range(nbatches): + istart = i * batch_size + iend = (i + 1) * batch_size + batch = texts[istart:iend] + if ids: + batch_ids = ids[istart:iend] + else: + batch_ids = [str(uuid.uuid4()) for _ in range(len(batch))] + if metadf is not None: + batch_meta = metadf.iloc[istart:iend].reset_index(drop=True) + else: + batch_meta = None + self._insert(batch, batch_ids, batch_meta) + out_ids = out_ids + batch_ids + return out_ids + + def add_documents( + self, documents: List[Document], batch_size: int = 32, **kwargs: Any + ) -> List[str]: + """Run more documents through the embeddings and add to the vectorstore. + + Args: + documents (List[Document]: Documents to add to the vectorstore. + batch_size (Optional[int]): Size of batch of documents to insert at once. + + Returns: + List[str]: List of IDs of the added texts. + """ + + try: + import pandas as pd + except ImportError: + raise ImportError( + "Could not import pandas python package. " + "Please install it with `pip install pandas`." + ) + + texts = [x.page_content for x in documents] + metadata = pd.DataFrame([x.metadata for x in documents]) + return self.add_texts(texts, metadata=metadata, batch_size=batch_size) + + def similarity_search_with_score( + self, + query: str, + k: int = 1, + filter: Optional[List] = [], + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + """Run similarity search with distance from a query string. + + Args: + query (str): Query string. + k (Optional[int]): number of neighbors to retrieve. + filter (Optional[List]): KDB.AI metadata filter clause: https://code.kx.com/kdbai/use/filter.html + + Returns: + List[Document]: List of similar documents. + """ + return self.similarity_search_by_vector_with_score( + self._embed_query(query), k=k, filter=filter, **kwargs + ) + + def similarity_search_by_vector_with_score( + self, + embedding: List[float], + *, + k: int = 1, + filter: Optional[List] = [], + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + """Return pinecone documents most similar to embedding, along with scores. + + Args: + embedding (List[float]): query vector. + k (Optional[int]): number of neighbors to retrieve. + filter (Optional[List]): KDB.AI metadata filter clause: https://code.kx.com/kdbai/use/filter.html + + Returns: + List[Document]: List of similar documents. + """ + if "n" in kwargs: + k = kwargs.pop("n") + matches = self._table.search(vectors=[embedding], n=k, filter=filter, **kwargs)[ + 0 + ] + docs = [] + for row in matches.to_dict(orient="records"): + text = row.pop("text") + score = row.pop("__nn_distance") + docs.append( + ( + Document( + page_content=text, + metadata={k: v for k, v in row.items() if k != "text"}, + ), + score, + ) + ) + return docs + + def similarity_search( + self, + query: str, + k: int = 1, + filter: Optional[List] = [], + **kwargs: Any, + ) -> List[Document]: + """Run similarity search from a query string. + + Args: + query (str): Query string. + k (Optional[int]): number of neighbors to retrieve. + filter (Optional[List]): KDB.AI metadata filter clause: https://code.kx.com/kdbai/use/filter.html + + Returns: + List[Document]: List of similar documents. + """ + docs_and_scores = self.similarity_search_with_score( + query, k=k, filter=filter, **kwargs + ) + return [doc for doc, _ in docs_and_scores] + + @classmethod + def from_texts( + cls: Any, + texts: List[str], + embedding: Embeddings, + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> Any: + """Not implemented.""" + raise Exception("Not implemented.") diff --git a/libs/community/tests/unit_tests/vectorstores/test_public_api.py b/libs/community/tests/unit_tests/vectorstores/test_public_api.py index 25db0bf3ac1e9..b94c8ae47ece5 100644 --- a/libs/community/tests/unit_tests/vectorstores/test_public_api.py +++ b/libs/community/tests/unit_tests/vectorstores/test_public_api.py @@ -28,6 +28,7 @@ "Epsilla", "FAISS", "Hologres", + "KDBAI", "LanceDB", "Lantern", "LLMRails", From d898d2f07b7bb86ae3913baaeaf246fe362e2a49 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev <eyurtsev@gmail.com> Date: Tue, 23 Jan 2024 21:41:44 -0500 Subject: [PATCH 203/215] docs: Fix version in which astream_events was released (#16481) Fix typo in version --- docs/docs/expression_language/interface.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/expression_language/interface.ipynb b/docs/docs/expression_language/interface.ipynb index 6837a73532a41..a0e63966afaa2 100644 --- a/docs/docs/expression_language/interface.ipynb +++ b/docs/docs/expression_language/interface.ipynb @@ -30,7 +30,7 @@ "- [`ainvoke`](#async-invoke): call the chain on an input async\n", "- [`abatch`](#async-batch): call the chain on a list of inputs async\n", "- [`astream_log`](#async-stream-intermediate-steps): stream back intermediate steps as they happen, in addition to the final response\n", - "- [`astream_events`](#async-stream-events): **beta** stream events as they happen in the chain (introduced in `langchain-core` 0.2.0)\n", + "- [`astream_events`](#async-stream-events): **beta** stream events as they happen in the chain (introduced in `langchain-core` 0.1.14)\n", "\n", "The **input type** and **output type** varies by component:\n", "\n", From 0e2e7d8b83215f2e24b85fd5324cd3ae6b2766b9 Mon Sep 17 00:00:00 2001 From: Krista Pratico <krpratic@microsoft.com> Date: Tue, 23 Jan 2024 18:48:29 -0800 Subject: [PATCH 204/215] langchain[patch]: allow passing client with OpenAIAssistantRunnable (#16486) - **Description:** This addresses the issue tagged below where if you try to pass your own client when creating an OpenAI assistant, a pydantic error is raised: Example code: ```python import openai from langchain.agents.openai_assistant import OpenAIAssistantRunnable client = openai.OpenAI() interpreter_assistant = OpenAIAssistantRunnable.create_assistant( name="langchain assistant", instructions="You are a personal math tutor. Write and run code to answer math questions.", tools=[{"type": "code_interpreter"}], model="gpt-4-1106-preview", client=client ) ``` Error: `pydantic.v1.errors.ConfigError: field "client" not yet prepared, so the type is still a ForwardRef. You might need to call OpenAIAssistantRunnable.update_forward_refs()` It additionally updates type hints and docstrings to indicate that an AzureOpenAI client is permissible as well. - **Issue:** https://github.com/langchain-ai/langchain/issues/15948 - **Dependencies:** N/A --- .../langchain/agents/openai_assistant/base.py | 11 +++++----- .../agents/test_openai_assistant.py | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 libs/langchain/tests/unit_tests/agents/test_openai_assistant.py diff --git a/libs/langchain/langchain/agents/openai_assistant/base.py b/libs/langchain/langchain/agents/openai_assistant/base.py index 84d99c8f97e97..67e361b02eb8e 100644 --- a/libs/langchain/langchain/agents/openai_assistant/base.py +++ b/libs/langchain/langchain/agents/openai_assistant/base.py @@ -146,8 +146,8 @@ def execute_agent(agent, tools, input): """ # noqa: E501 - client: openai.OpenAI = Field(default_factory=_get_openai_client) - """OpenAI client.""" + client: Any = Field(default_factory=_get_openai_client) + """OpenAI or AzureOpenAI client.""" assistant_id: str """OpenAI assistant id.""" check_every_ms: float = 1_000.0 @@ -163,7 +163,7 @@ def create_assistant( tools: Sequence[Union[BaseTool, dict]], model: str, *, - client: Optional[openai.OpenAI] = None, + client: Optional[Union[openai.OpenAI, openai.AzureOpenAI]] = None, **kwargs: Any, ) -> OpenAIAssistantRunnable: """Create an OpenAI Assistant and instantiate the Runnable. @@ -173,7 +173,8 @@ def create_assistant( instructions: Assistant instructions. tools: Assistant tools. Can be passed in OpenAI format or as BaseTools. model: Assistant model to use. - client: OpenAI client. Will create default client if not specified. + client: OpenAI or AzureOpenAI client. + Will create default OpenAI client if not specified. Returns: OpenAIAssistantRunnable configured to run using the created assistant. @@ -191,7 +192,7 @@ def create_assistant( tools=openai_tools, model=model, ) - return cls(assistant_id=assistant.id, **kwargs) + return cls(assistant_id=assistant.id, client=client, **kwargs) def invoke( self, input: dict, config: Optional[RunnableConfig] = None diff --git a/libs/langchain/tests/unit_tests/agents/test_openai_assistant.py b/libs/langchain/tests/unit_tests/agents/test_openai_assistant.py new file mode 100644 index 0000000000000..aaa4ba48d1df0 --- /dev/null +++ b/libs/langchain/tests/unit_tests/agents/test_openai_assistant.py @@ -0,0 +1,21 @@ +import pytest + +from langchain.agents.openai_assistant import OpenAIAssistantRunnable + + +@pytest.mark.requires("openai") +def test_user_supplied_client() -> None: + import openai + + client = openai.AzureOpenAI( + azure_endpoint="azure_endpoint", + api_key="api_key", + api_version="api_version", + ) + + assistant = OpenAIAssistantRunnable( + assistant_id="assistant_id", + client=client, + ) + + assert assistant.client == client From 5c6e12375732ebdaa971401f936b2f7783d61679 Mon Sep 17 00:00:00 2001 From: Serena Ruan <82044803+serena-ruan@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:09:02 -0800 Subject: [PATCH 205/215] community[patch]: Fix MlflowCallback with none artifacts_dir (#16487) --- libs/community/langchain_community/callbacks/mlflow_callback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/callbacks/mlflow_callback.py b/libs/community/langchain_community/callbacks/mlflow_callback.py index 577532a361684..4070d17d61cf9 100644 --- a/libs/community/langchain_community/callbacks/mlflow_callback.py +++ b/libs/community/langchain_community/callbacks/mlflow_callback.py @@ -275,7 +275,7 @@ def __init__( tags: Optional[Dict] = None, tracking_uri: Optional[str] = None, run_id: Optional[str] = None, - artifacts_dir: Optional[str] = None, + artifacts_dir: str = "", ) -> None: """Initialize callback handler.""" import_pandas() From 80fcc50c6570b3456d3b2db0d61017992553d2ff Mon Sep 17 00:00:00 2001 From: Ali Zendegani <zendegani@gmail.com> Date: Wed, 24 Jan 2024 04:19:53 +0100 Subject: [PATCH 206/215] langchain[patch]: Minor Fix: Enable Passing custom_headers for Authentication in GraphQL Agent/Tool (#16413) - **Description:** This PR aims to enhance the `langchain` library by enabling the support for passing `custom_headers` in the `GraphQLAPIWrapper` usage within `langchain/agents/load_tools.py`. While the `GraphQLAPIWrapper` from the `langchain_community` module is inherently capable of handling `custom_headers`, its current invocation in `load_tools.py` does not facilitate this functionality. This limitation restricts the use of the `graphql` tool with databases or APIs that require token-based authentication. The absence of support for `custom_headers` in this context also leads to a lack of error messages when attempting to interact with secured GraphQL endpoints, making debugging and troubleshooting more challenging. This update modifies the `load_tools` function to correctly handle `custom_headers`, thereby allowing secure and authenticated access to GraphQL services requiring tokens. Example usage after the proposed change: ```python tools = load_tools( ["graphql"], graphql_endpoint="https://your-graphql-endpoint.com/graphql", custom_headers={"Authorization": f"Token {api_token}"}, ) ``` - **Issue:** None, - **Dependencies:** None, - **Twitter handle:** None --- libs/langchain/langchain/agents/load_tools.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/langchain/langchain/agents/load_tools.py b/libs/langchain/langchain/agents/load_tools.py index ab3a34cfee72d..24adaf867df71 100644 --- a/libs/langchain/langchain/agents/load_tools.py +++ b/libs/langchain/langchain/agents/load_tools.py @@ -353,9 +353,7 @@ def _get_scenexplain(**kwargs: Any) -> BaseTool: def _get_graphql_tool(**kwargs: Any) -> BaseTool: - graphql_endpoint = kwargs["graphql_endpoint"] - wrapper = GraphQLAPIWrapper(graphql_endpoint=graphql_endpoint) - return BaseGraphQLTool(graphql_wrapper=wrapper) + return BaseGraphQLTool(graphql_wrapper=GraphQLAPIWrapper(**kwargs)) def _get_openweathermap(**kwargs: Any) -> BaseTool: @@ -455,7 +453,7 @@ def _get_reddit_search(**kwargs: Any) -> BaseTool: ), "stackexchange": (_get_stackexchange, []), "sceneXplain": (_get_scenexplain, []), - "graphql": (_get_graphql_tool, ["graphql_endpoint"]), + "graphql": (_get_graphql_tool, ["graphql_endpoint", "custom_headers"]), "openweathermap-api": (_get_openweathermap, ["openweathermap_api_key"]), "dataforseo-api-search": ( _get_dataforseo_api_search, From 019b6ebe8d6d97227ec48af3d1fe7220b22f5b0e Mon Sep 17 00:00:00 2001 From: Xudong Sun <sun.xd@hotmail.com> Date: Wed, 24 Jan 2024 11:23:46 +0800 Subject: [PATCH 207/215] community[minor]: Add iFlyTek Spark LLM chat model support (#13389) - **Description:** This PR enables LangChain to access the iFlyTek's Spark LLM via the chat_models wrapper. - **Dependencies:** websocket-client ^1.6.1 - **Tag maintainer:** @baskaryan ### SparkLLM chat model usage Get SparkLLM's app_id, api_key and api_secret from [iFlyTek SparkLLM API Console](https://console.xfyun.cn/services/bm3) (for more info, see [iFlyTek SparkLLM Intro](https://xinghuo.xfyun.cn/sparkapi) ), then set environment variables `IFLYTEK_SPARK_APP_ID`, `IFLYTEK_SPARK_API_KEY` and `IFLYTEK_SPARK_API_SECRET` or pass parameters when using it like the demo below: ```python3 from langchain.chat_models.sparkllm import ChatSparkLLM client = ChatSparkLLM( spark_app_id="<app_id>", spark_api_key="<api_key>", spark_api_secret="<api_secret>" ) ``` --- docs/docs/integrations/chat/sparkllm.ipynb | 99 ++++ .../chat_models/__init__.py | 2 + .../chat_models/sparkllm.py | 473 ++++++++++++++++++ .../chat_models/test_sparkllm.py | 36 ++ .../unit_tests/chat_models/test_imports.py | 1 + 5 files changed, 611 insertions(+) create mode 100644 docs/docs/integrations/chat/sparkllm.ipynb create mode 100644 libs/community/langchain_community/chat_models/sparkllm.py create mode 100644 libs/community/tests/integration_tests/chat_models/test_sparkllm.py diff --git a/docs/docs/integrations/chat/sparkllm.ipynb b/docs/docs/integrations/chat/sparkllm.ipynb new file mode 100644 index 0000000000000..4fe68f9a2a709 --- /dev/null +++ b/docs/docs/integrations/chat/sparkllm.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3ddface67cd10a87", + "metadata": { + "collapsed": false + }, + "source": [ + "# SparkLLM Chat\n", + "\n", + "SparkLLM chat models API by iFlyTek. For more information, see [iFlyTek Open Platform](https://www.xfyun.cn/)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic use" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43daa39972d4c533", + "metadata": { + "collapsed": false, + "is_executing": true + }, + "outputs": [], + "source": [ + "\"\"\"For basic init and call\"\"\"\n", + "from langchain.chat_models import ChatSparkLLM\n", + "from langchain.schema import HumanMessage\n", + "\n", + "chat = ChatSparkLLM(\n", + " spark_app_id=\"<app_id>\", spark_api_key=\"<api_key>\", spark_api_secret=\"<api_secret>\"\n", + ")\n", + "message = HumanMessage(content=\"Hello\")\n", + "chat([message])" + ] + }, + { + "cell_type": "markdown", + "id": "df755f4c5689510", + "metadata": { + "collapsed": false + }, + "source": [ + "- Get SparkLLM's app_id, api_key and api_secret from [iFlyTek SparkLLM API Console](https://console.xfyun.cn/services/bm3) (for more info, see [iFlyTek SparkLLM Intro](https://xinghuo.xfyun.cn/sparkapi) ), then set environment variables `IFLYTEK_SPARK_APP_ID`, `IFLYTEK_SPARK_API_KEY` and `IFLYTEK_SPARK_API_SECRET` or pass parameters when creating `ChatSparkLLM` as the demo above." + ] + }, + { + "cell_type": "markdown", + "id": "984e32ee47bc6772", + "metadata": { + "collapsed": false + }, + "source": [ + "## For ChatSparkLLM with Streaming" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7dc162bd65fec08f", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "chat = ChatSparkLLM(streaming=True)\n", + "for chunk in chat.stream(\"Hello!\"):\n", + " print(chunk.content, end=\"\")" + ] + } + ], + "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/community/langchain_community/chat_models/__init__.py b/libs/community/langchain_community/chat_models/__init__.py index 067a57038ba13..bc35a3c4ef6ae 100644 --- a/libs/community/langchain_community/chat_models/__init__.py +++ b/libs/community/langchain_community/chat_models/__init__.py @@ -48,6 +48,7 @@ from langchain_community.chat_models.openai import ChatOpenAI from langchain_community.chat_models.pai_eas_endpoint import PaiEasChatEndpoint from langchain_community.chat_models.promptlayer_openai import PromptLayerChatOpenAI +from langchain_community.chat_models.sparkllm import ChatSparkLLM from langchain_community.chat_models.tongyi import ChatTongyi from langchain_community.chat_models.vertexai import ChatVertexAI from langchain_community.chat_models.volcengine_maas import VolcEngineMaasChat @@ -88,6 +89,7 @@ "ChatBaichuan", "ChatHunyuan", "GigaChat", + "ChatSparkLLM", "VolcEngineMaasChat", "GPTRouter", "ChatZhipuAI", diff --git a/libs/community/langchain_community/chat_models/sparkllm.py b/libs/community/langchain_community/chat_models/sparkllm.py new file mode 100644 index 0000000000000..7e84c2e98c2e5 --- /dev/null +++ b/libs/community/langchain_community/chat_models/sparkllm.py @@ -0,0 +1,473 @@ +import base64 +import hashlib +import hmac +import json +import logging +import queue +import threading +from datetime import datetime +from queue import Queue +from time import mktime +from typing import Any, Dict, Generator, Iterator, List, Mapping, Optional, Type +from urllib.parse import urlencode, urlparse, urlunparse +from wsgiref.handlers import format_date_time + +from langchain_core.callbacks import ( + CallbackManagerForLLMRun, +) +from langchain_core.language_models.chat_models import ( + BaseChatModel, + generate_from_stream, +) +from langchain_core.messages import ( + AIMessage, + AIMessageChunk, + BaseMessage, + BaseMessageChunk, + ChatMessage, + ChatMessageChunk, + HumanMessage, + HumanMessageChunk, + SystemMessage, +) +from langchain_core.outputs import ( + ChatGeneration, + ChatGenerationChunk, + ChatResult, +) +from langchain_core.pydantic_v1 import Field, root_validator +from langchain_core.utils import ( + get_from_dict_or_env, + get_pydantic_field_names, +) + +logger = logging.getLogger(__name__) + + +def _convert_message_to_dict(message: BaseMessage) -> dict: + if isinstance(message, ChatMessage): + message_dict = {"role": "user", "content": message.content} + elif isinstance(message, HumanMessage): + message_dict = {"role": "user", "content": message.content} + elif isinstance(message, AIMessage): + message_dict = {"role": "assistant", "content": message.content} + elif isinstance(message, SystemMessage): + message_dict = {"role": "system", "content": message.content} + else: + raise ValueError(f"Got unknown type {message}") + + return message_dict + + +def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage: + msg_role = _dict["role"] + msg_content = _dict["content"] + if msg_role == "user": + return HumanMessage(content=msg_content) + elif msg_role == "assistant": + content = msg_content or "" + return AIMessage(content=content) + elif msg_role == "system": + return SystemMessage(content=msg_content) + else: + return ChatMessage(content=msg_content, role=msg_role) + + +def _convert_delta_to_message_chunk( + _dict: Mapping[str, Any], default_class: Type[BaseMessageChunk] +) -> BaseMessageChunk: + msg_role = _dict["role"] + msg_content = _dict.get("content", "") + if msg_role == "user" or default_class == HumanMessageChunk: + return HumanMessageChunk(content=msg_content) + elif msg_role == "assistant" or default_class == AIMessageChunk: + return AIMessageChunk(content=msg_content) + elif msg_role or default_class == ChatMessageChunk: + return ChatMessageChunk(content=msg_content, role=msg_role) + else: + return default_class(content=msg_content) + + +class ChatSparkLLM(BaseChatModel): + """Wrapper around iFlyTek's Spark large language model. + + To use, you should pass `app_id`, `api_key`, `api_secret` + as a named parameter to the constructor OR set environment + variables ``IFLYTEK_SPARK_APP_ID``, ``IFLYTEK_SPARK_API_KEY`` and + ``IFLYTEK_SPARK_API_SECRET`` + + Example: + .. code-block:: python + + client = ChatSparkLLM( + spark_app_id="<app_id>", + spark_api_key="<api_key>", + spark_api_secret="<api_secret>" + ) + """ + + @classmethod + def is_lc_serializable(cls) -> bool: + """Return whether this model can be serialized by Langchain.""" + return False + + @property + def lc_secrets(self) -> Dict[str, str]: + return { + "spark_app_id": "IFLYTEK_SPARK_APP_ID", + "spark_api_key": "IFLYTEK_SPARK_API_KEY", + "spark_api_secret": "IFLYTEK_SPARK_API_SECRET", + "spark_api_url": "IFLYTEK_SPARK_API_URL", + "spark_llm_domain": "IFLYTEK_SPARK_LLM_DOMAIN", + } + + client: Any = None #: :meta private: + spark_app_id: Optional[str] = None + spark_api_key: Optional[str] = None + spark_api_secret: Optional[str] = None + spark_api_url: Optional[str] = None + spark_llm_domain: Optional[str] = None + spark_user_id: str = "lc_user" + streaming: bool = False + request_timeout: int = 30 + temperature: float = 0.5 + top_k: int = 4 + model_kwargs: Dict[str, Any] = Field(default_factory=dict) + + @root_validator(pre=True) + def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """Build extra kwargs from additional params that were passed in.""" + all_required_field_names = get_pydantic_field_names(cls) + extra = values.get("model_kwargs", {}) + for field_name in list(values): + if field_name in extra: + raise ValueError(f"Found {field_name} supplied twice.") + if field_name not in all_required_field_names: + logger.warning( + f"""WARNING! {field_name} is not default parameter. + {field_name} was transferred to model_kwargs. + Please confirm that {field_name} is what you intended.""" + ) + extra[field_name] = values.pop(field_name) + + invalid_model_kwargs = all_required_field_names.intersection(extra.keys()) + if invalid_model_kwargs: + raise ValueError( + f"Parameters {invalid_model_kwargs} should be specified explicitly. " + f"Instead they were passed in as part of `model_kwargs` parameter." + ) + + values["model_kwargs"] = extra + + return values + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + values["spark_app_id"] = get_from_dict_or_env( + values, + "spark_app_id", + "IFLYTEK_SPARK_APP_ID", + ) + values["spark_api_key"] = get_from_dict_or_env( + values, + "spark_api_key", + "IFLYTEK_SPARK_API_KEY", + ) + values["spark_api_secret"] = get_from_dict_or_env( + values, + "spark_api_secret", + "IFLYTEK_SPARK_API_SECRET", + ) + values["spark_app_url"] = get_from_dict_or_env( + values, + "spark_app_url", + "IFLYTEK_SPARK_APP_URL", + "wss://spark-api.xf-yun.com/v3.1/chat", + ) + values["spark_llm_domain"] = get_from_dict_or_env( + values, + "spark_llm_domain", + "IFLYTEK_SPARK_LLM_DOMAIN", + "generalv3", + ) + # put extra params into model_kwargs + values["model_kwargs"]["temperature"] = values["temperature"] or cls.temperature + values["model_kwargs"]["top_k"] = values["top_k"] or cls.top_k + + values["client"] = _SparkLLMClient( + app_id=values["spark_app_id"], + api_key=values["spark_api_key"], + api_secret=values["spark_api_secret"], + api_url=values["spark_api_url"], + spark_domain=values["spark_llm_domain"], + model_kwargs=values["model_kwargs"], + ) + return values + + def _stream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[ChatGenerationChunk]: + default_chunk_class = AIMessageChunk + + self.client.arun( + [_convert_message_to_dict(m) for m in messages], + self.spark_user_id, + self.model_kwargs, + self.streaming, + ) + for content in self.client.subscribe(timeout=self.request_timeout): + if "data" not in content: + continue + delta = content["data"] + chunk = _convert_delta_to_message_chunk(delta, default_chunk_class) + yield ChatGenerationChunk(message=chunk) + if run_manager: + run_manager.on_llm_new_token(str(chunk.content)) + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + if self.streaming: + stream_iter = self._stream( + messages=messages, stop=stop, run_manager=run_manager, **kwargs + ) + return generate_from_stream(stream_iter) + + self.client.arun( + [_convert_message_to_dict(m) for m in messages], + self.spark_user_id, + self.model_kwargs, + False, + ) + completion = {} + llm_output = {} + for content in self.client.subscribe(timeout=self.request_timeout): + if "usage" in content: + llm_output["token_usage"] = content["usage"] + if "data" not in content: + continue + completion = content["data"] + message = _convert_dict_to_message(completion) + generations = [ChatGeneration(message=message)] + return ChatResult(generations=generations, llm_output=llm_output) + + @property + def _llm_type(self) -> str: + return "spark-llm-chat" + + +class _SparkLLMClient: + """ + Use websocket-client to call the SparkLLM interface provided by Xfyun, + which is the iFlyTek's open platform for AI capabilities + """ + + def __init__( + self, + app_id: str, + api_key: str, + api_secret: str, + api_url: Optional[str] = None, + spark_domain: Optional[str] = None, + model_kwargs: Optional[dict] = None, + ): + try: + import websocket + + self.websocket_client = websocket + except ImportError: + raise ImportError( + "Could not import websocket client python package. " + "Please install it with `pip install websocket-client`." + ) + + self.api_url = ( + "wss://spark-api.xf-yun.com/v3.1/chat" if not api_url else api_url + ) + self.app_id = app_id + self.ws_url = _SparkLLMClient._create_url( + self.api_url, + api_key, + api_secret, + ) + self.model_kwargs = model_kwargs + self.spark_domain = spark_domain or "generalv3" + self.queue: Queue[Dict] = Queue() + self.blocking_message = {"content": "", "role": "assistant"} + + @staticmethod + def _create_url(api_url: str, api_key: str, api_secret: str) -> str: + """ + Generate a request url with an api key and an api secret. + """ + # generate timestamp by RFC1123 + date = format_date_time(mktime(datetime.now().timetuple())) + + # urlparse + parsed_url = urlparse(api_url) + host = parsed_url.netloc + path = parsed_url.path + + signature_origin = f"host: {host}\ndate: {date}\nGET {path} HTTP/1.1" + + # encrypt using hmac-sha256 + signature_sha = hmac.new( + api_secret.encode("utf-8"), + signature_origin.encode("utf-8"), + digestmod=hashlib.sha256, + ).digest() + + signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding="utf-8") + + authorization_origin = f'api_key="{api_key}", algorithm="hmac-sha256", \ + headers="host date request-line", signature="{signature_sha_base64}"' + authorization = base64.b64encode(authorization_origin.encode("utf-8")).decode( + encoding="utf-8" + ) + + # generate url + params_dict = {"authorization": authorization, "date": date, "host": host} + encoded_params = urlencode(params_dict) + url = urlunparse( + ( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + parsed_url.params, + encoded_params, + parsed_url.fragment, + ) + ) + return url + + def run( + self, + messages: List[Dict], + user_id: str, + model_kwargs: Optional[dict] = None, + streaming: bool = False, + ) -> None: + self.websocket_client.enableTrace(False) + ws = self.websocket_client.WebSocketApp( + self.ws_url, + on_message=self.on_message, + on_error=self.on_error, + on_close=self.on_close, + on_open=self.on_open, + ) + ws.messages = messages + ws.user_id = user_id + ws.model_kwargs = self.model_kwargs if model_kwargs is None else model_kwargs + ws.streaming = streaming + ws.run_forever() + + def arun( + self, + messages: List[Dict], + user_id: str, + model_kwargs: Optional[dict] = None, + streaming: bool = False, + ) -> threading.Thread: + ws_thread = threading.Thread( + target=self.run, + args=( + messages, + user_id, + model_kwargs, + streaming, + ), + ) + ws_thread.start() + return ws_thread + + def on_error(self, ws: Any, error: Optional[Any]) -> None: + self.queue.put({"error": error}) + ws.close() + + def on_close(self, ws: Any, close_status_code: int, close_reason: str) -> None: + logger.debug( + { + "log": { + "close_status_code": close_status_code, + "close_reason": close_reason, + } + } + ) + self.queue.put({"done": True}) + + def on_open(self, ws: Any) -> None: + self.blocking_message = {"content": "", "role": "assistant"} + data = json.dumps( + self.gen_params( + messages=ws.messages, user_id=ws.user_id, model_kwargs=ws.model_kwargs + ) + ) + ws.send(data) + + def on_message(self, ws: Any, message: str) -> None: + data = json.loads(message) + code = data["header"]["code"] + if code != 0: + self.queue.put( + {"error": f"Code: {code}, Error: {data['header']['message']}"} + ) + ws.close() + else: + choices = data["payload"]["choices"] + status = choices["status"] + content = choices["text"][0]["content"] + if ws.streaming: + self.queue.put({"data": choices["text"][0]}) + else: + self.blocking_message["content"] += content + if status == 2: + if not ws.streaming: + self.queue.put({"data": self.blocking_message}) + usage_data = ( + data.get("payload", {}).get("usage", {}).get("text", {}) + if data + else {} + ) + self.queue.put({"usage": usage_data}) + ws.close() + + def gen_params( + self, messages: list, user_id: str, model_kwargs: Optional[dict] = None + ) -> dict: + data: Dict = { + "header": {"app_id": self.app_id, "uid": user_id}, + "parameter": {"chat": {"domain": self.spark_domain}}, + "payload": {"message": {"text": messages}}, + } + + if model_kwargs: + data["parameter"]["chat"].update(model_kwargs) + logger.debug(f"Spark Request Parameters: {data}") + return data + + def subscribe(self, timeout: Optional[int] = 30) -> Generator[Dict, None, None]: + while True: + try: + content = self.queue.get(timeout=timeout) + except queue.Empty as _: + raise TimeoutError( + f"SparkLLMClient wait LLM api response timeout {timeout} seconds" + ) + if "error" in content: + raise ConnectionError(content["error"]) + if "usage" in content: + yield content + continue + if "done" in content: + break + if "data" not in content: + break + yield content diff --git a/libs/community/tests/integration_tests/chat_models/test_sparkllm.py b/libs/community/tests/integration_tests/chat_models/test_sparkllm.py new file mode 100644 index 0000000000000..a219b8857420a --- /dev/null +++ b/libs/community/tests/integration_tests/chat_models/test_sparkllm.py @@ -0,0 +1,36 @@ +from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage + +from langchain_community.chat_models.sparkllm import ChatSparkLLM + + +def test_chat_spark_llm() -> None: + chat = ChatSparkLLM() + message = HumanMessage(content="Hello") + response = chat([message]) + assert isinstance(response, AIMessage) + assert isinstance(response.content, str) + + +def test_chat_spark_llm_streaming() -> None: + chat = ChatSparkLLM(streaming=True) + for chunk in chat.stream("Hello!"): + assert isinstance(chunk, AIMessageChunk) + assert isinstance(chunk.content, str) + + +def test_chat_spark_llm_with_domain() -> None: + chat = ChatSparkLLM(spark_llm_domain="generalv3") + message = HumanMessage(content="Hello") + response = chat([message]) + print(response) + assert isinstance(response, AIMessage) + assert isinstance(response.content, str) + + +def test_chat_spark_llm_with_temperature() -> None: + chat = ChatSparkLLM(temperature=0.9, top_k=2) + message = HumanMessage(content="Hello") + response = chat([message]) + print(response) + assert isinstance(response, AIMessage) + assert isinstance(response.content, str) diff --git a/libs/community/tests/unit_tests/chat_models/test_imports.py b/libs/community/tests/unit_tests/chat_models/test_imports.py index 187459afd5019..29d3529f7545e 100644 --- a/libs/community/tests/unit_tests/chat_models/test_imports.py +++ b/libs/community/tests/unit_tests/chat_models/test_imports.py @@ -33,6 +33,7 @@ "ChatBaichuan", "ChatHunyuan", "GigaChat", + "ChatSparkLLM", "VolcEngineMaasChat", "LlamaEdgeChatService", "GPTRouter", From 476bf8b763395aad993e98060f204421aef33656 Mon Sep 17 00:00:00 2001 From: Raunak <rksrivastava100@gmail.com> Date: Wed, 24 Jan 2024 09:07:37 +0530 Subject: [PATCH 208/215] community[patch]: Load list of files using UnstructuredFileLoader (#16216) - **Description:** Updated `_get_elements()` function of `UnstructuredFileLoader `class to check if the argument self.file_path is a file or list of files. If it is a list of files then it iterates over the list of file paths, calls the partition function for each one, and appends the results to the elements list. If self.file_path is not a list, it calls the partition function as before. - **Issue:** Fixed #15607, - **Dependencies:** NA - **Twitter handle:** NA Co-authored-by: H161961 <Raunak.Raunak@Honeywell.com> --- .../document_loaders/unstructured_file.ipynb | 52 ++++++++++++++++++- .../document_loaders/unstructured.py | 8 ++- .../document_loaders/test_unstructured.py | 17 ++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/docs/docs/integrations/document_loaders/unstructured_file.ipynb b/docs/docs/integrations/document_loaders/unstructured_file.ipynb index 113e42bf2bb61..0d26030ab7ef9 100644 --- a/docs/docs/integrations/document_loaders/unstructured_file.ipynb +++ b/docs/docs/integrations/document_loaders/unstructured_file.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "2886982e", "metadata": {}, "outputs": [], @@ -100,6 +100,54 @@ "docs[0].page_content[:400]" ] }, + { + "cell_type": "markdown", + "id": "b4ab0a79", + "metadata": {}, + "source": [ + "### Load list of files" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "092d9a0b", + "metadata": {}, + "outputs": [], + "source": [ + "files = [\"./example_data/whatsapp_chat.txt\", \"./example_data/layout-parser-paper.pdf\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f841c4f8", + "metadata": {}, + "outputs": [], + "source": [ + "loader = UnstructuredFileLoader(files)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "993c240b", + "metadata": {}, + "outputs": [], + "source": [ + "docs = loader.load()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ce4ff07", + "metadata": {}, + "outputs": [], + "source": [ + "docs[0].page_content[:400]" + ] + }, { "cell_type": "markdown", "id": "7874d01d", @@ -495,7 +543,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.9.0" } }, "nbformat": 4, diff --git a/libs/community/langchain_community/document_loaders/unstructured.py b/libs/community/langchain_community/document_loaders/unstructured.py index 9d8223ff86072..b7ee7717056ed 100644 --- a/libs/community/langchain_community/document_loaders/unstructured.py +++ b/libs/community/langchain_community/document_loaders/unstructured.py @@ -170,7 +170,13 @@ def __init__( def _get_elements(self) -> List: from unstructured.partition.auto import partition - return partition(filename=self.file_path, **self.unstructured_kwargs) + if isinstance(self.file_path, list): + elements = [] + for file in self.file_path: + elements.extend(partition(filename=file, **self.unstructured_kwargs)) + return elements + else: + return partition(filename=self.file_path, **self.unstructured_kwargs) def _get_metadata(self) -> dict: return {"source": self.file_path} diff --git a/libs/community/tests/integration_tests/document_loaders/test_unstructured.py b/libs/community/tests/integration_tests/document_loaders/test_unstructured.py index bb1d809ca5d0e..5bdd30f2c2e2a 100644 --- a/libs/community/tests/integration_tests/document_loaders/test_unstructured.py +++ b/libs/community/tests/integration_tests/document_loaders/test_unstructured.py @@ -28,6 +28,23 @@ def add_the_end(text: str) -> str: assert docs[0].page_content.endswith("THE END!") +def test_unstructured_file_loader_multiple_files() -> None: + """Test unstructured loader.""" + file_paths = [ + os.path.join(EXAMPLE_DOCS_DIRECTORY, "layout-parser-paper.pdf"), + os.path.join(EXAMPLE_DOCS_DIRECTORY, "whatsapp_chat.txt"), + ] + + loader = UnstructuredFileLoader( + file_path=file_paths, + strategy="fast", + mode="elements", + ) + docs = loader.load() + + assert len(docs) > 1 + + def test_unstructured_api_file_loader() -> None: """Test unstructured loader.""" file_path = os.path.join(EXAMPLE_DOCS_DIRECTORY, "layout-parser-paper.pdf") From 2b2285dac0d6ae0f6b7c09c33882a0d5be26c078 Mon Sep 17 00:00:00 2001 From: BeatrixCohere <128378696+BeatrixCohere@users.noreply.github.com> Date: Wed, 24 Jan 2024 03:39:42 +0000 Subject: [PATCH 209/215] docs: Update cohere rerank and comparison docs (#16198) - **Description:** Update the cohere rerank docs to use cohere embeddings - **Issue:** n/a - **Dependencies:** n/a - **Twitter handle:** n/a --- docs/docs/guides/model_laboratory.ipynb | 28 +- .../retrievers/cohere-reranker.ipynb | 250 ++++++++---------- 2 files changed, 127 insertions(+), 151 deletions(-) diff --git a/docs/docs/guides/model_laboratory.ipynb b/docs/docs/guides/model_laboratory.ipynb index b6533de4c0d07..540bc4023fb6b 100644 --- a/docs/docs/guides/model_laboratory.ipynb +++ b/docs/docs/guides/model_laboratory.ipynb @@ -35,6 +35,22 @@ "from langchain_openai import OpenAI" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "3dd69cb4", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# get a new token: https://dashboard.cohere.ai/\n", + "os.environ[\"COHERE_API_KEY\"] = getpass.getpass(\"Cohere API Key:\")\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Open API Key:\")\n", + "os.environ[\"HUGGINGFACEHUB_API_TOKEN\"] = getpass.getpass(\"Hugging Face API Key:\")" + ] + }, { "cell_type": "code", "execution_count": 2, @@ -44,7 +60,7 @@ "source": [ "llms = [\n", " OpenAI(temperature=0),\n", - " Cohere(model=\"command-xlarge-20221108\", max_tokens=20, temperature=0),\n", + " Cohere(temperature=0),\n", " HuggingFaceHub(repo_id=\"google/flan-t5-xl\", model_kwargs={\"temperature\": 1}),\n", "]" ] @@ -160,7 +176,7 @@ " llm=open_ai_llm, search_chain=search, verbose=True\n", ")\n", "\n", - "cohere_llm = Cohere(temperature=0, model=\"command-xlarge-20221108\")\n", + "cohere_llm = Cohere(temperature=0)\n", "search = SerpAPIWrapper()\n", "self_ask_with_search_cohere = SelfAskWithSearchChain(\n", " llm=cohere_llm, search_chain=search, verbose=True\n", @@ -241,14 +257,6 @@ "source": [ "model_lab.compare(\"What is the hometown of the reigning men's U.S. Open champion?\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94159131", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/docs/integrations/retrievers/cohere-reranker.ipynb b/docs/docs/integrations/retrievers/cohere-reranker.ipynb index afdf5e2f5881c..24c58ff5d4861 100644 --- a/docs/docs/integrations/retrievers/cohere-reranker.ipynb +++ b/docs/docs/integrations/retrievers/cohere-reranker.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "b37bd138-4f3c-4d2c-bc4b-be705ce27a09", "metadata": { "tags": [] @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "c47b0b26-6d51-4beb-aedb-ad09740a9a2b", "metadata": {}, "outputs": [], @@ -55,19 +55,12 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "2268c17f-5cc3-457b-928b-0d470154c3a8", - "metadata": {}, - "outputs": [], - "source": [ - "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "28e8dc12", - "metadata": {}, + "execution_count": 14, + "id": "6fa3d916", + "metadata": { + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, "outputs": [], "source": [ "# Helper function for printing docs\n", @@ -95,8 +88,8 @@ }, { "cell_type": "code", - "execution_count": 22, - "id": "9fbcc58f", + "execution_count": 15, + "id": "b7648612", "metadata": {}, "outputs": [ { @@ -111,28 +104,20 @@ "----------------------------------------------------------------------------------------------------\n", "Document 2:\n", "\n", - "As I said last year, especially to our younger transgender Americans, I will always have your back as your President, so you can be yourself and reach your God-given potential. \n", + "We cannot let this happen. \n", "\n", - "While it often appears that we never agree, that isn’t true. I signed 80 bipartisan bills into law last year. From preventing government shutdowns to protecting Asian-Americans from still-too-common hate crimes to reforming military justice.\n", + "Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n", + "\n", + "Tonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service.\n", "----------------------------------------------------------------------------------------------------\n", "Document 3:\n", "\n", - "A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder. Since she’s been nominated, she’s received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n", + "As I said last year, especially to our younger transgender Americans, I will always have your back as your President, so you can be yourself and reach your God-given potential. \n", "\n", - "And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system.\n", + "While it often appears that we never agree, that isn’t true. I signed 80 bipartisan bills into law last year. From preventing government shutdowns to protecting Asian-Americans from still-too-common hate crimes to reforming military justice.\n", "----------------------------------------------------------------------------------------------------\n", "Document 4:\n", "\n", - "He met the Ukrainian people. \n", - "\n", - "From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n", - "\n", - "Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. \n", - "\n", - "In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight.\n", - "----------------------------------------------------------------------------------------------------\n", - "Document 5:\n", - "\n", "I spoke with their families and told them that we are forever in debt for their sacrifice, and we will carry on their mission to restore the trust and safety every community deserves. \n", "\n", "I’ve worked on these issues a long time. \n", @@ -141,199 +126,190 @@ "\n", "So let’s not abandon our streets. Or choose between safety and equal justice.\n", "----------------------------------------------------------------------------------------------------\n", - "Document 6:\n", - "\n", - "Vice President Harris and I ran for office with a new economic vision for America. \n", + "Document 5:\n", "\n", - "Invest in America. Educate Americans. Grow the workforce. Build the economy from the bottom up \n", - "and the middle out, not from the top down. \n", + "He met the Ukrainian people. \n", "\n", - "Because we know that when the middle class grows, the poor have a ladder up and the wealthy do very well. \n", + "From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n", "\n", - "America used to have the best roads, bridges, and airports on Earth. \n", + "Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. \n", "\n", - "Now our infrastructure is ranked 13th in the world.\n", + "In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight.\n", "----------------------------------------------------------------------------------------------------\n", - "Document 7:\n", + "Document 6:\n", "\n", - "And tonight, I’m announcing that the Justice Department will name a chief prosecutor for pandemic fraud. \n", + "So let’s not abandon our streets. Or choose between safety and equal justice. \n", "\n", - "By the end of this year, the deficit will be down to less than half what it was before I took office. \n", + "Let’s come together to protect our communities, restore trust, and hold law enforcement accountable. \n", "\n", - "The only president ever to cut the deficit by more than one trillion dollars in a single year. \n", + "That’s why the Justice Department required body cameras, banned chokeholds, and restricted no-knock warrants for its officers.\n", + "----------------------------------------------------------------------------------------------------\n", + "Document 7:\n", "\n", - "Lowering your costs also means demanding more competition. \n", + "But that trickle-down theory led to weaker economic growth, lower wages, bigger deficits, and the widest gap between those at the top and everyone else in nearly a century. \n", "\n", - "I’m a capitalist, but capitalism without competition isn’t capitalism. \n", + "Vice President Harris and I ran for office with a new economic vision for America. \n", "\n", - "It’s exploitation—and it drives up prices.\n", + "Invest in America. Educate Americans. Grow the workforce. Build the economy from the bottom up \n", + "and the middle out, not from the top down.\n", "----------------------------------------------------------------------------------------------------\n", "Document 8:\n", "\n", - "For the past 40 years we were told that if we gave tax breaks to those at the very top, the benefits would trickle down to everyone else. \n", - "\n", - "But that trickle-down theory led to weaker economic growth, lower wages, bigger deficits, and the widest gap between those at the top and everyone else in nearly a century. \n", + "A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder. Since she’s been nominated, she’s received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n", "\n", - "Vice President Harris and I ran for office with a new economic vision for America.\n", + "And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system.\n", "----------------------------------------------------------------------------------------------------\n", "Document 9:\n", "\n", - "All told, we created 369,000 new manufacturing jobs in America just last year. \n", + "The widow of Sergeant First Class Heath Robinson. \n", "\n", - "Powered by people I’ve met like JoJo Burgess, from generations of union steelworkers from Pittsburgh, who’s here with us tonight. \n", + "He was born a soldier. Army National Guard. Combat medic in Kosovo and Iraq. \n", "\n", - "As Ohio Senator Sherrod Brown says, “It’s time to bury the label “Rust Belt.” \n", + "Stationed near Baghdad, just yards from burn pits the size of football fields. \n", "\n", - "It’s time. \n", + "Heath’s widow Danielle is here with us tonight. They loved going to Ohio State football games. He loved building Legos with their daughter. \n", + "\n", + "But cancer from prolonged exposure to burn pits ravaged Heath’s lungs and body. \n", "\n", - "But with all the bright spots in our economy, record job growth and higher wages, too many families are struggling to keep up with the bills.\n", + "Danielle says Heath was a fighter to the very end.\n", "----------------------------------------------------------------------------------------------------\n", "Document 10:\n", "\n", - "I’m also calling on Congress: pass a law to make sure veterans devastated by toxic exposures in Iraq and Afghanistan finally get the benefits and comprehensive health care they deserve. \n", + "As I’ve told Xi Jinping, it is never a good bet to bet against the American people. \n", "\n", - "And fourth, let’s end cancer as we know it. \n", + "We’ll create good jobs for millions of Americans, modernizing roads, airports, ports, and waterways all across America. \n", "\n", - "This is personal to me and Jill, to Kamala, and to so many of you. \n", - "\n", - "Cancer is the #2 cause of death in America–second only to heart disease.\n", + "And we’ll do it all to withstand the devastating effects of the climate crisis and promote environmental justice.\n", "----------------------------------------------------------------------------------------------------\n", "Document 11:\n", "\n", - "He will never extinguish their love of freedom. He will never weaken the resolve of the free world. \n", + "As Ohio Senator Sherrod Brown says, “It’s time to bury the label “Rust Belt.” \n", "\n", - "We meet tonight in an America that has lived through two of the hardest years this nation has ever faced. \n", + "It’s time. \n", "\n", - "The pandemic has been punishing. \n", + "But with all the bright spots in our economy, record job growth and higher wages, too many families are struggling to keep up with the bills. \n", "\n", - "And so many families are living paycheck to paycheck, struggling to keep up with the rising cost of food, gas, housing, and so much more. \n", + "Inflation is robbing them of the gains they might otherwise feel. \n", "\n", - "I understand.\n", + "I get it. That’s why my top priority is getting prices under control.\n", "----------------------------------------------------------------------------------------------------\n", "Document 12:\n", "\n", - "Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \n", + "This was a bipartisan effort, and I want to thank the members of both parties who worked to make it happen. \n", "\n", - "Last year COVID-19 kept us apart. This year we are finally together again. \n", + "We’re done talking about infrastructure weeks. \n", "\n", - "Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n", + "We’re going to have an infrastructure decade. \n", "\n", - "With a duty to one another to the American people to the Constitution. \n", + "It is going to transform America and put us on a path to win the economic competition of the 21st Century that we face with the rest of the world—particularly with China. \n", "\n", - "And with an unwavering resolve that freedom will always triumph over tyranny.\n", + "As I’ve told Xi Jinping, it is never a good bet to bet against the American people.\n", "----------------------------------------------------------------------------------------------------\n", "Document 13:\n", "\n", - "I know. \n", - "\n", - "One of those soldiers was my son Major Beau Biden. \n", - "\n", - "We don’t know for sure if a burn pit was the cause of his brain cancer, or the diseases of so many of our troops. \n", + "He will never extinguish their love of freedom. He will never weaken the resolve of the free world. \n", "\n", - "But I’m committed to finding out everything we can. \n", + "We meet tonight in an America that has lived through two of the hardest years this nation has ever faced. \n", "\n", - "Committed to military families like Danielle Robinson from Ohio. \n", + "The pandemic has been punishing. \n", "\n", - "The widow of Sergeant First Class Heath Robinson. \n", + "And so many families are living paycheck to paycheck, struggling to keep up with the rising cost of food, gas, housing, and so much more. \n", "\n", - "He was born a soldier. Army National Guard. Combat medic in Kosovo and Iraq.\n", + "I understand.\n", "----------------------------------------------------------------------------------------------------\n", "Document 14:\n", "\n", - "And soon, we’ll strengthen the Violence Against Women Act that I first wrote three decades ago. It is important for us to show the nation that we can come together and do big things. \n", + "I understand. \n", + "\n", + "I remember when my Dad had to leave our home in Scranton, Pennsylvania to find work. I grew up in a family where if the price of food went up, you felt it. \n", "\n", - "So tonight I’m offering a Unity Agenda for the Nation. Four big things we can do together. \n", + "That’s why one of the first things I did as President was fight to pass the American Rescue Plan. \n", "\n", - "First, beat the opioid epidemic. \n", + "Because people were hurting. We needed to act, and we did. \n", "\n", - "There is so much we can do. Increase funding for prevention, treatment, harm reduction, and recovery.\n", + "Few pieces of legislation have done more in a critical moment in our history to lift us out of crisis.\n", "----------------------------------------------------------------------------------------------------\n", "Document 15:\n", "\n", - "Third, support our veterans. \n", + "My administration is providing assistance with job training and housing, and now helping lower-income veterans get VA care debt-free. \n", "\n", - "Veterans are the best of us. \n", + "Our troops in Iraq and Afghanistan faced many dangers. \n", "\n", - "I’ve always believed that we have a sacred obligation to equip all those we send to war and care for them and their families when they come home. \n", + "One was stationed at bases and breathing in toxic smoke from “burn pits” that incinerated wastes of war—medical and hazard material, jet fuel, and more. \n", "\n", - "My administration is providing assistance with job training and housing, and now helping lower-income veterans get VA care debt-free. \n", + "When they came home, many of the world’s fittest and best trained warriors were never the same. \n", "\n", - "Our troops in Iraq and Afghanistan faced many dangers.\n", + "Headaches. Numbness. Dizziness.\n", "----------------------------------------------------------------------------------------------------\n", "Document 16:\n", "\n", - "When we invest in our workers, when we build the economy from the bottom up and the middle out together, we can do something we haven’t done in a long time: build a better America. \n", + "Danielle says Heath was a fighter to the very end. \n", "\n", - "For more than two years, COVID-19 has impacted every decision in our lives and the life of the nation. \n", - "\n", - "And I know you’re tired, frustrated, and exhausted. \n", - "\n", - "But I also know this.\n", - "----------------------------------------------------------------------------------------------------\n", - "Document 17:\n", + "He didn’t know how to stop fighting, and neither did she. \n", "\n", - "Now is the hour. \n", + "Through her pain she found purpose to demand we do better. \n", "\n", - "Our moment of responsibility. \n", + "Tonight, Danielle—we are. \n", "\n", - "Our test of resolve and conscience, of history itself. \n", + "The VA is pioneering new ways of linking toxic exposures to diseases, already helping more veterans get benefits. \n", "\n", - "It is in this moment that our character is formed. Our purpose is found. Our future is forged. \n", + "And tonight, I’m announcing we’re expanding eligibility to veterans suffering from nine respiratory cancers.\n", + "----------------------------------------------------------------------------------------------------\n", + "Document 17:\n", "\n", - "Well I know this nation. \n", + "Cancer is the #2 cause of death in America–second only to heart disease. \n", "\n", - "We will meet the test. \n", + "Last month, I announced our plan to supercharge \n", + "the Cancer Moonshot that President Obama asked me to lead six years ago. \n", "\n", - "To protect freedom and liberty, to expand fairness and opportunity. \n", + "Our goal is to cut the cancer death rate by at least 50% over the next 25 years, turn more cancers from death sentences into treatable diseases. \n", "\n", - "We will save democracy. \n", + "More support for patients and families. \n", "\n", - "As hard as these times have been, I am more optimistic about America today than I have been my whole life.\n", + "To get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health.\n", "----------------------------------------------------------------------------------------------------\n", "Document 18:\n", "\n", - "He didn’t know how to stop fighting, and neither did she. \n", - "\n", - "Through her pain she found purpose to demand we do better. \n", - "\n", - "Tonight, Danielle—we are. \n", + "My plan to fight inflation will lower your costs and lower the deficit. \n", "\n", - "The VA is pioneering new ways of linking toxic exposures to diseases, already helping more veterans get benefits. \n", + "17 Nobel laureates in economics say my plan will ease long-term inflationary pressures. Top business leaders and most Americans support my plan. And here’s the plan: \n", "\n", - "And tonight, I’m announcing we’re expanding eligibility to veterans suffering from nine respiratory cancers.\n", + "First – cut the cost of prescription drugs. Just look at insulin. One in ten Americans has diabetes. In Virginia, I met a 13-year-old boy named Joshua Davis.\n", "----------------------------------------------------------------------------------------------------\n", "Document 19:\n", "\n", - "I understand. \n", - "\n", - "I remember when my Dad had to leave our home in Scranton, Pennsylvania to find work. I grew up in a family where if the price of food went up, you felt it. \n", + "Let’s pass the Paycheck Fairness Act and paid leave. \n", "\n", - "That’s why one of the first things I did as President was fight to pass the American Rescue Plan. \n", + "Raise the minimum wage to $15 an hour and extend the Child Tax Credit, so no one has to raise a family in poverty. \n", "\n", - "Because people were hurting. We needed to act, and we did. \n", + "Let’s increase Pell Grants and increase our historic support of HBCUs, and invest in what Jill—our First Lady who teaches full-time—calls America’s best-kept secret: community colleges. \n", "\n", - "Few pieces of legislation have done more in a critical moment in our history to lift us out of crisis.\n", + "And let’s pass the PRO Act when a majority of workers want to form a union—they shouldn’t be stopped.\n", "----------------------------------------------------------------------------------------------------\n", "Document 20:\n", "\n", - "So let’s not abandon our streets. Or choose between safety and equal justice. \n", + "Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \n", "\n", - "Let’s come together to protect our communities, restore trust, and hold law enforcement accountable. \n", + "Last year COVID-19 kept us apart. This year we are finally together again. \n", + "\n", + "Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n", + "\n", + "With a duty to one another to the American people to the Constitution. \n", "\n", - "That’s why the Justice Department required body cameras, banned chokeholds, and restricted no-knock warrants for its officers.\n" + "And with an unwavering resolve that freedom will always triumph over tyranny.\n" ] } ], "source": [ "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", "from langchain_community.document_loaders import TextLoader\n", + "from langchain_community.embeddings import CohereEmbeddings\n", "from langchain_community.vectorstores import FAISS\n", - "from langchain_openai import OpenAIEmbeddings\n", "\n", "documents = TextLoader(\"../../modules/state_of_the_union.txt\").load()\n", "text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)\n", "texts = text_splitter.split_documents(documents)\n", - "retriever = FAISS.from_documents(texts, OpenAIEmbeddings()).as_retriever(\n", + "retriever = FAISS.from_documents(texts, CohereEmbeddings()).as_retriever(\n", " search_kwargs={\"k\": 20}\n", ")\n", "\n", @@ -353,8 +329,8 @@ }, { "cell_type": "code", - "execution_count": 31, - "id": "9a658023", + "execution_count": 16, + "id": "b83dfedb", "metadata": {}, "outputs": [ { @@ -388,9 +364,9 @@ "source": [ "from langchain.retrievers import ContextualCompressionRetriever\n", "from langchain.retrievers.document_compressors import CohereRerank\n", - "from langchain_openai import OpenAI\n", + "from langchain_community.llms import Cohere\n", "\n", - "llm = OpenAI(temperature=0)\n", + "llm = Cohere(temperature=0)\n", "compressor = CohereRerank()\n", "compression_retriever = ContextualCompressionRetriever(\n", " base_compressor=compressor, base_retriever=retriever\n", @@ -412,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 17, "id": "367dafe0", "metadata": {}, "outputs": [], @@ -422,19 +398,19 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 18, "id": "ae697ca4", "metadata": {}, "outputs": [], "source": [ "chain = RetrievalQA.from_chain_type(\n", - " llm=OpenAI(temperature=0), retriever=compression_retriever\n", + " llm=Cohere(temperature=0), retriever=compression_retriever\n", ")" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 19, "id": "46ee62fc", "metadata": {}, "outputs": [ @@ -442,10 +418,10 @@ "data": { "text/plain": [ "{'query': 'What did the president say about Ketanji Brown Jackson',\n", - " 'result': \" The president said that Ketanji Brown Jackson is one of the nation's top legal minds and that she is a consensus builder who has received a broad range of support from the Fraternal Order of Police to former judges appointed by Democrats and Republicans.\"}" + " 'result': \" The president speaks highly of Ketanji Brown Jackson, stating that she is one of the nation's top legal minds, and will continue the legacy of excellence of Justice Breyer. The president also mentions that he worked with her family and that she comes from a family of public school educators and police officers. Since her nomination, she has received support from various groups, including the Fraternal Order of Police and judges from both major political parties. \\n\\nWould you like me to extract another sentence from the provided text? \"}" ] }, - "execution_count": 34, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -453,14 +429,6 @@ "source": [ "chain({\"query\": query})" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "700a8133", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 4c7755778d922e45bf2d1de15c6ac0c1c8ef81e4 Mon Sep 17 00:00:00 2001 From: Karim Lalani <jimmy00784@gmail.com> Date: Tue, 23 Jan 2024 21:46:19 -0600 Subject: [PATCH 210/215] community[patch]: SurrealDB fix for asyncio (#16092) Code fix for asyncio --- .../community/langchain_community/document_loaders/surrealdb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/community/langchain_community/document_loaders/surrealdb.py b/libs/community/langchain_community/document_loaders/surrealdb.py index c5df9406dbfb9..3a96a14a1adbf 100644 --- a/libs/community/langchain_community/document_loaders/surrealdb.py +++ b/libs/community/langchain_community/document_loaders/surrealdb.py @@ -46,8 +46,6 @@ def __init__( self.sdb = Surreal(self.dburl) self.kwargs = kwargs - asyncio.run(self.initialize()) - async def initialize(self) -> None: """ Initialize connection to surrealdb database From d628a80a5d4a0e1bb9d79e37483d24187b792c2a Mon Sep 17 00:00:00 2001 From: Alessio Serra <48280936+alessioserra@users.noreply.github.com> Date: Wed, 24 Jan 2024 05:04:15 +0100 Subject: [PATCH 211/215] community[patch]: added 'conversational' as a valid task for hugginface endopoint models (#15761) - **Description:** added the conversational task to hugginFace endpoint in order to use models designed for chatbot programming. - **Dependencies:** None --------- Co-authored-by: Alessio Serra (ext.) <alessio.serra@partner.bmw.de> Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> Co-authored-by: Bagatur <baskaryan@gmail.com> --- .../langchain_community/llms/huggingface_endpoint.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/llms/huggingface_endpoint.py b/libs/community/langchain_community/llms/huggingface_endpoint.py index d429e2fd93557..c14b2e24a8050 100644 --- a/libs/community/langchain_community/llms/huggingface_endpoint.py +++ b/libs/community/langchain_community/llms/huggingface_endpoint.py @@ -8,7 +8,12 @@ from langchain_community.llms.utils import enforce_stop_tokens -VALID_TASKS = ("text2text-generation", "text-generation", "summarization") +VALID_TASKS = ( + "text2text-generation", + "text-generation", + "summarization", + "conversational", +) class HuggingFaceEndpoint(LLM): @@ -144,6 +149,8 @@ def _call( text = generated_text[0]["generated_text"] elif self.task == "summarization": text = generated_text[0]["summary_text"] + elif self.task == "conversational": + text = generated_text["response"][1] else: raise ValueError( f"Got invalid task {self.task}, " From 61da2ff24c6b9b4246912a4766f8e8b36b899981 Mon Sep 17 00:00:00 2001 From: chyroc <chyroc@qq.com> Date: Wed, 24 Jan 2024 12:08:53 +0800 Subject: [PATCH 212/215] community[patch]: use SecretStr for yandex model secrets (#15463) --- .../langchain_community/embeddings/yandex.py | 24 ++++++++++--------- .../langchain_community/llms/yandex.py | 22 ++++++++++------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/libs/community/langchain_community/embeddings/yandex.py b/libs/community/langchain_community/embeddings/yandex.py index d96fb46869ab1..4022eb1199bb3 100644 --- a/libs/community/langchain_community/embeddings/yandex.py +++ b/libs/community/langchain_community/embeddings/yandex.py @@ -5,8 +5,8 @@ from typing import Any, Callable, Dict, List from langchain_core.embeddings import Embeddings -from langchain_core.pydantic_v1 import BaseModel, root_validator -from langchain_core.utils import get_from_dict_or_env +from langchain_core.pydantic_v1 import BaseModel, SecretStr, root_validator +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env from tenacity import ( before_sleep_log, retry, @@ -41,18 +41,16 @@ class YandexGPTEmbeddings(BaseModel, Embeddings): embeddings = YandexGPTEmbeddings(iam_token="t1.9eu...", model_uri="emb://<folder-id>/text-search-query/latest") """ - iam_token: str = "" + iam_token: SecretStr = "" """Yandex Cloud IAM token for service account with the `ai.languageModels.user` role""" - api_key: str = "" + api_key: SecretStr = "" """Yandex Cloud Api Key for service account with the `ai.languageModels.user` role""" model_uri: str = "" """Model uri to use.""" folder_id: str = "" """Yandex Cloud folder ID""" - model_uri: str = "" - """Model uri to use.""" model_name: str = "text-search-query" """Model name to use.""" model_version: str = "latest" @@ -66,23 +64,27 @@ class YandexGPTEmbeddings(BaseModel, Embeddings): def validate_environment(cls, values: Dict) -> Dict: """Validate that iam token exists in environment.""" - iam_token = get_from_dict_or_env(values, "iam_token", "YC_IAM_TOKEN", "") + iam_token = convert_to_secret_str( + get_from_dict_or_env(values, "iam_token", "YC_IAM_TOKEN", "") + ) values["iam_token"] = iam_token - api_key = get_from_dict_or_env(values, "api_key", "YC_API_KEY", "") + api_key = convert_to_secret_str( + get_from_dict_or_env(values, "api_key", "YC_API_KEY", "") + ) values["api_key"] = api_key folder_id = get_from_dict_or_env(values, "folder_id", "YC_FOLDER_ID", "") values["folder_id"] = folder_id - if api_key == "" and iam_token == "": + if api_key.get_secret_value() == "" and iam_token.get_secret_value() == "": raise ValueError("Either 'YC_API_KEY' or 'YC_IAM_TOKEN' must be provided.") if values["iam_token"]: values["_grpc_metadata"] = [ - ("authorization", f"Bearer {values['iam_token']}") + ("authorization", f"Bearer {values['iam_token'].get_secret_value()}") ] if values["folder_id"]: values["_grpc_metadata"].append(("x-folder-id", values["folder_id"])) else: values["_grpc_metadata"] = ( - ("authorization", f"Api-Key {values['api_key']}"), + ("authorization", f"Api-Key {values['api_key'].get_secret_value()}"), ) if values["model_uri"] == "" and values["folder_id"] == "": raise ValueError("Either 'model_uri' or 'folder_id' must be provided.") diff --git a/libs/community/langchain_community/llms/yandex.py b/libs/community/langchain_community/llms/yandex.py index c07efe6831046..c96570357ce16 100644 --- a/libs/community/langchain_community/llms/yandex.py +++ b/libs/community/langchain_community/llms/yandex.py @@ -9,8 +9,8 @@ ) from langchain_core.language_models.llms import LLM from langchain_core.load.serializable import Serializable -from langchain_core.pydantic_v1 import root_validator -from langchain_core.utils import get_from_dict_or_env +from langchain_core.pydantic_v1 import SecretStr, root_validator +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env from tenacity import ( before_sleep_log, retry, @@ -25,10 +25,10 @@ class _BaseYandexGPT(Serializable): - iam_token: str = "" + iam_token: SecretStr = "" """Yandex Cloud IAM token for service or user account with the `ai.languageModels.user` role""" - api_key: str = "" + api_key: SecretStr = "" """Yandex Cloud Api Key for service account with the `ai.languageModels.user` role""" folder_id: str = "" @@ -72,24 +72,28 @@ def _identifying_params(self) -> Mapping[str, Any]: def validate_environment(cls, values: Dict) -> Dict: """Validate that iam token exists in environment.""" - iam_token = get_from_dict_or_env(values, "iam_token", "YC_IAM_TOKEN", "") + iam_token = convert_to_secret_str( + get_from_dict_or_env(values, "iam_token", "YC_IAM_TOKEN", "") + ) values["iam_token"] = iam_token - api_key = get_from_dict_or_env(values, "api_key", "YC_API_KEY", "") + api_key = convert_to_secret_str( + get_from_dict_or_env(values, "api_key", "YC_API_KEY", "") + ) values["api_key"] = api_key folder_id = get_from_dict_or_env(values, "folder_id", "YC_FOLDER_ID", "") values["folder_id"] = folder_id - if api_key == "" and iam_token == "": + if api_key.get_secret_value() == "" and iam_token.get_secret_value() == "": raise ValueError("Either 'YC_API_KEY' or 'YC_IAM_TOKEN' must be provided.") if values["iam_token"]: values["_grpc_metadata"] = [ - ("authorization", f"Bearer {values['iam_token']}") + ("authorization", f"Bearer {values['iam_token'].get_secret_value()}") ] if values["folder_id"]: values["_grpc_metadata"].append(("x-folder-id", values["folder_id"])) else: values["_grpc_metadata"] = ( - ("authorization", f"Api-Key {values['api_key']}"), + ("authorization", f"Api-Key {values['api_key'].get_secret_value()}"), ) if values["model_uri"] == "" and values["folder_id"] == "": raise ValueError("Either 'model_uri' or 'folder_id' must be provided.") From 3f38e1a4575169e3be4cce8eea56efc29d4d70c4 Mon Sep 17 00:00:00 2001 From: Nuno Campos <nuno@langchain.dev> Date: Tue, 23 Jan 2024 20:22:37 -0800 Subject: [PATCH 213/215] Remove double line (#16426) <!-- Thank you for contributing to LangChain! Please title your PR "<package>: <description>", where <package> is whichever of langchain, community, core, experimental, etc. is being modified. Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes if applicable, - **Dependencies:** any dependencies required for this change, - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` from the root of the package you've modified to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ 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. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> --- libs/core/langchain_core/runnables/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/core/langchain_core/runnables/utils.py b/libs/core/langchain_core/runnables/utils.py index 03edb0c291432..cb8f21e29ce0f 100644 --- a/libs/core/langchain_core/runnables/utils.py +++ b/libs/core/langchain_core/runnables/utils.py @@ -254,7 +254,6 @@ def get_function_nonlocals(func: Callable) -> List[Any]: vv = getattr(vv, part) else: values.append(vv) - values.append(vv) return values except (SyntaxError, TypeError, OSError): return [] From b3ed98dec070f3721a8b87c00fec7c2732ef181e Mon Sep 17 00:00:00 2001 From: bachr <dzlabs@outlook.com> Date: Tue, 23 Jan 2024 21:09:43 -0800 Subject: [PATCH 214/215] community[patch]: avoid KeyError when language not in LANGUAGE_SEGMENTERS (#15212) **Description:** Handle unsupported languages in same way as when none is provided **Issue:** The following line will throw a KeyError if the language is not supported. ```python self.Segmenter = LANGUAGE_SEGMENTERS[language] ``` E.g. when using `Language.CPP` we would get `KeyError: <Language.CPP: 'cpp'>` --------- Co-authored-by: Bagatur <baskaryan@gmail.com> --- .../document_loaders/parsers/language/language_parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/community/langchain_community/document_loaders/parsers/language/language_parser.py b/libs/community/langchain_community/document_loaders/parsers/language/language_parser.py index c53dd493fdeed..b3f6534327f55 100644 --- a/libs/community/langchain_community/document_loaders/parsers/language/language_parser.py +++ b/libs/community/langchain_community/document_loaders/parsers/language/language_parser.py @@ -97,6 +97,8 @@ def __init__(self, language: Optional[Language] = None, parser_threshold: int = language: If None (default), it will try to infer language from source. parser_threshold: Minimum lines needed to activate parsing (0 by default). """ + if language and language not in LANGUAGE_SEGMENTERS: + raise Exception(f"No parser available for {language}") self.language = language self.parser_threshold = parser_threshold From 9e95699277fe0db3bfee1654276a43bfba9ecc64 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin <jeremi@newlogic.com> Date: Wed, 24 Jan 2024 12:42:29 +0700 Subject: [PATCH 215/215] community[patch]: Fix error message when litellm is not installed (#16316) The error message was mentioning the wrong package. I updated it to the correct one. --- libs/community/langchain_community/chat_models/litellm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/langchain_community/chat_models/litellm.py b/libs/community/langchain_community/chat_models/litellm.py index cb6a2107d34eb..39f7284c3b5ac 100644 --- a/libs/community/langchain_community/chat_models/litellm.py +++ b/libs/community/langchain_community/chat_models/litellm.py @@ -246,8 +246,8 @@ def validate_environment(cls, values: Dict) -> Dict: import litellm except ImportError: raise ChatLiteLLMException( - "Could not import google.generativeai python package. " - "Please install it with `pip install google-generativeai`" + "Could not import litellm python package. " + "Please install it with `pip install litellm`" ) values["openai_api_key"] = get_from_dict_or_env(