From 02fc428878d2a0433007fe8329f4967ea2aa868b Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 25 Mar 2024 16:29:18 +0100 Subject: [PATCH 01/32] first commit, new llm project --- llm-complete-guide/.dockerignore | 9 + llm-complete-guide/LICENSE | 15 ++ llm-complete-guide/README.md | 128 +++++++++++++ llm-complete-guide/materializers/__init__.py | 16 ++ llm-complete-guide/pipelines/__init__.py | 17 ++ llm-complete-guide/pipelines/llm_basic_rag.py | 11 ++ llm-complete-guide/requirements.txt | 3 + llm-complete-guide/run.py | 67 +++++++ llm-complete-guide/steps/__init__.py | 16 ++ llm-complete-guide/steps/url_scraper.py | 50 +++++ .../steps/url_scraping_utils.py | 175 ++++++++++++++++++ llm-complete-guide/steps/web_url_loader.py | 35 ++++ 12 files changed, 542 insertions(+) create mode 100644 llm-complete-guide/.dockerignore create mode 100644 llm-complete-guide/LICENSE create mode 100644 llm-complete-guide/README.md create mode 100644 llm-complete-guide/materializers/__init__.py create mode 100644 llm-complete-guide/pipelines/__init__.py create mode 100644 llm-complete-guide/pipelines/llm_basic_rag.py create mode 100644 llm-complete-guide/requirements.txt create mode 100644 llm-complete-guide/run.py create mode 100644 llm-complete-guide/steps/__init__.py create mode 100644 llm-complete-guide/steps/url_scraper.py create mode 100644 llm-complete-guide/steps/url_scraping_utils.py create mode 100644 llm-complete-guide/steps/web_url_loader.py diff --git a/llm-complete-guide/.dockerignore b/llm-complete-guide/.dockerignore new file mode 100644 index 00000000..496552c8 --- /dev/null +++ b/llm-complete-guide/.dockerignore @@ -0,0 +1,9 @@ +* +!/pipelines/** +!/steps/** +!/materializers/** +!/evaluate/** +!/finetune/** +!/generate/** +!/lit_gpt/** +!/scripts/** diff --git a/llm-complete-guide/LICENSE b/llm-complete-guide/LICENSE new file mode 100644 index 00000000..75d01fb4 --- /dev/null +++ b/llm-complete-guide/LICENSE @@ -0,0 +1,15 @@ +Apache Software License 2.0 + +Copyright (c) ZenML GmbH 2024. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/llm-complete-guide/README.md b/llm-complete-guide/README.md new file mode 100644 index 00000000..94267385 --- /dev/null +++ b/llm-complete-guide/README.md @@ -0,0 +1,128 @@ +# ☮️ Fine-tuning open source LLMs using MLOps pipelines + +Welcome to your newly generated "ZenML LLM Finetuning project" project! This is +a great way to get hands-on with ZenML using production-like template. +The project contains a collection of ZenML steps, pipelines and other artifacts +and useful resources that can serve as a solid starting point for finetuning open-source LLMs using ZenML. + +Using these pipelines, we can run the data-preparation and model finetuning with a single command while using YAML files for [configuration](https://docs.zenml.io/user-guide/production-guide/configure-pipeline) and letting ZenML take care of tracking our metadata and [containerizing our pipelines](https://docs.zenml.io/user-guide/advanced-guide/infrastructure-management/containerize-your-pipeline). + +
+
+ + Model version metadata + +
+
+ +## :earth_americas: Inspiration and Credit + +This project heavily relies on the [Lit-GPT project](https://github.com/Lightning-AI/litgpt) of the amazing people at Lightning AI. We used [this blogpost](https://lightning.ai/pages/community/lora-insights/#toc14) to get started with LoRA and QLoRA and modified the commands they recommend to make them work using ZenML. + +## 🏃 How to run + +In this project we provide a few predefined configuration files for finetuning models on the [Alpaca](https://huggingface.co/datasets/tatsu-lab/alpaca) dataset. Before we're able to run any pipeline, we need to set up our environment as follows: + +```bash +# Set up a Python virtual environment, if you haven't already +python3 -m venv .venv +source .venv/bin/activate + +# Install requirements +pip install -r requirements.txt +``` + +### Combined feature engineering and finetuning pipeline + +The easiest way to get started with just a single command is to run the finetuning pipeline with the `finetune-alpaca.yaml` configuration file, which will do both feature engineering and finetuning: + +```shell +python run.py --finetuning-pipeline --config finetune-alpaca.yaml +``` + +When running the pipeline like this, the trained adapter will be stored in the ZenML artifact store. You can optionally upload the adapter, the merged model or both by specifying the `adapter_output_repo` and `merged_output_repo` parameters in the configuration file. + + +### Evaluation pipeline + +Before running this pipeline, you will need to fill in the `adapter_repo` in the `eval.yaml` configuration file. This should point to a huggingface repository that contains the finetuned adapter you got by running the finetuning pipeline. + +```shell +python run.py --eval-pipeline --config eval.yaml +``` + +### Merging pipeline + +In case you have trained an adapter using the finetuning pipeline, you can merge it with the base model by filling in the `adapter_repo` and `output_repo` parameters in the `merge.yaml` file, and then running: + +```shell +python run.py --merge-pipeline --config merge.yaml +``` + +### Feature Engineering followed by Finetuning + +If you want to finetune your model on a different dataset, you can do so by running the feature engineering pipeline followed by the finetuning pipeline. To define your dataset, take a look at the `scripts/prepare_*` scripts and set the dataset name in the `feature-alpaca.yaml` config file. + +```shell +python run.py --feature-pipeline --config feature-alpaca.yaml +python run.py --finetuning-pipeline --config finetune-from-dataset.yaml +``` + +## ☁️ Running with a remote stack + +To finetune an LLM on remote infrastructure, you can either use a remote orchestrator or a remote step operator. Follow these steps to set up a complete remote stack: +- Register the [orchestrator](https://docs.zenml.io/stacks-and-components/component-guide/orchestrators) (or [step operator](https://docs.zenml.io/stacks-and-components/component-guide/step-operators)) and make sure to configure it in a way so that the finetuning step has access to a GPU with at least 24GB of VRAM. Check out our docs for more [details](https://docs.zenml.io/stacks-and-components/component-guide). + - To access GPUs with this amount of VRAM, you might need to increase your GPU quota ([AWS](https://docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html), [GCP](https://console.cloud.google.com/iam-admin/quotas), [Azure](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas?view=azureml-api-2#request-quota-and-limit-increases)). + - The GPU instance that your finetuning will be running on will have CUDA drivers of a specific version installed. If that CUDA version is not compatible with the one provided by the default Docker image of the finetuning pipeline, you will need to modify it in the configuration file. See [here](https://hub.docker.com/r/pytorch/pytorch/tags) for a list of available PyTorch images. + - If you're running out of memory, you can experiment with quantized LoRA (QLoRA) by setting a different value for the `quantize` parameter in the configuration, or reduce the `global_batch_size`/`micro_batch_size`. +- Register a remote [artifact store](https://docs.zenml.io/stacks-and-components/component-guide/artifact-stores) and [container registry](https://docs.zenml.io/stacks-and-components/component-guide/container-registries). +- Register a stack with all these components + ```shell + zenml stack register llm-finetuning-stack -o \ + -a \ + -c \ + [-s ] + ``` + +## 💾 Running with custom data + +To finetune a model with your custom data, you will need to convert it to a CSV file with the columns described +[here](https://github.com/Lightning-AI/litgpt/blob/main/tutorials/prepare_dataset.md#preparing-custom-datasets-from-a-csv-file). + +Next, update the `configs/feature-custom.yaml` file and set the value of the `csv_path` parameter to that CSV file. +With all that in place, you can now run the feature engineering pipeline to convert your CSV into the correct format for training and then run the finetuning pipeline as follows: +```shell +python run.py --feature-pipeline --config feature-custom.yaml +python run.py --finetuning-pipeline --config finetune-from-dataset.yaml +``` + +## 📜 Project Structure + +The project loosely follows [the recommended ZenML project structure](https://docs.zenml.io/user-guide/starter-guide/follow-best-practices): + +``` +. +├── configs # pipeline configuration files +│ ├── eval.yaml # configuration for the evaluation pipeline +│ ├── feature-alpaca.yaml # configuration for the feature engineering pipeline +│ ├── feature-custom.yaml # configuration for the feature engineering pipeline +│ ├── finetune-alpaca.yaml # configuration for the finetuning pipeline +│ ├── finetune-from-dataset.yaml # configuration for the finetuning pipeline +│ └── merge.yaml # configuration for the merging pipeline +├── pipelines # `zenml.pipeline` implementations +│ ├── evaluate.py # Evaluation pipeline +│ ├── feature_engineering.py # Feature engineering pipeline +│ ├── finetuning.py # Finetuning pipeline +│ └── merge.py # Merging pipeline +├── steps # logically grouped `zenml.steps` implementations +│ ├── evaluate.py # evaluate model performance +│ ├── feature_engineering.py # preprocess data +│ ├── finetune.py # finetune a model +│ ├── merge.py # merge model and adapter +│ ├── params.py # shared parameters for steps +│ └── utils.py # utility functions +├── .dockerignore +├── README.md # this file +├── requirements.txt # extra Python dependencies +└── run.py # CLI tool to run pipelines on ZenML Stack +``` diff --git a/llm-complete-guide/materializers/__init__.py b/llm-complete-guide/materializers/__init__.py new file mode 100644 index 00000000..757bd841 --- /dev/null +++ b/llm-complete-guide/materializers/__init__.py @@ -0,0 +1,16 @@ +# Apache Software License 2.0 +# +# Copyright (c) ZenML GmbH 2024. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/llm-complete-guide/pipelines/__init__.py b/llm-complete-guide/pipelines/__init__.py new file mode 100644 index 00000000..820059e9 --- /dev/null +++ b/llm-complete-guide/pipelines/__init__.py @@ -0,0 +1,17 @@ +# Apache Software License 2.0 +# +# Copyright (c) ZenML GmbH 2024. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from pipelines.llm_basic_rag import llm_basic_rag diff --git a/llm-complete-guide/pipelines/llm_basic_rag.py b/llm-complete-guide/pipelines/llm_basic_rag.py new file mode 100644 index 00000000..7b94317d --- /dev/null +++ b/llm-complete-guide/pipelines/llm_basic_rag.py @@ -0,0 +1,11 @@ +from zenml import pipeline +from steps.url_scraper import url_scraper +from steps.web_url_loader import web_url_loader + + +@pipeline +def llm_basic_rag() -> None: + """Pipeline to train a basic RAG model.""" + + urls = url_scraper() + web_url_loader(urls=urls) diff --git a/llm-complete-guide/requirements.txt b/llm-complete-guide/requirements.txt new file mode 100644 index 00000000..b6f4853e --- /dev/null +++ b/llm-complete-guide/requirements.txt @@ -0,0 +1,3 @@ +# zenml +langchain-community +ratelimit diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py new file mode 100644 index 00000000..dc5e1930 --- /dev/null +++ b/llm-complete-guide/run.py @@ -0,0 +1,67 @@ +# Apache Software License 2.0 +# +# Copyright (c) ZenML GmbH 2024. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +from typing import Optional + +import click +from zenml.logger import get_logger + +from pipelines import ( + llm_basic_rag, +) + +logger = get_logger(__name__) + + +@click.command( + help=""" +ZenML LLM Complete Guide project CLI v0.1.0. + +Run the ZenML LLM RAG complete guide project pipelines. + +Examples: + + \b + # Run the feature feature engineering pipeline + python run.py --feature-pipeline +""" +) +@click.option( + "--basic-rag", + "basic_rag", + is_flag=True, + default=False, + help="Whether to run the pipeline that creates the dataset.", +) +def main( + basic_rag: bool = False, + no_cache: bool = False, +): + """Main entry point for the pipeline execution. + + Args: + no_cache: If `True` cache will be disabled. + """ + pipeline_args = {"enable_cache": not no_cache} + + if basic_rag: + llm_basic_rag.with_options(**pipeline_args)() + + +if __name__ == "__main__": + main() diff --git a/llm-complete-guide/steps/__init__.py b/llm-complete-guide/steps/__init__.py new file mode 100644 index 00000000..757bd841 --- /dev/null +++ b/llm-complete-guide/steps/__init__.py @@ -0,0 +1,16 @@ +# Apache Software License 2.0 +# +# Copyright (c) ZenML GmbH 2024. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/llm-complete-guide/steps/url_scraper.py b/llm-complete-guide/steps/url_scraper.py new file mode 100644 index 00000000..000ef8cd --- /dev/null +++ b/llm-complete-guide/steps/url_scraper.py @@ -0,0 +1,50 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from typing import List +from typing_extensions import Annotated +from steps.url_scraping_utils import get_all_pages, get_nested_readme_urls +from zenml import step, log_artifact_metadata + + +@step +def url_scraper( + docs_url: str = "https://docs.zenml.io", + repo_url: str = "https://github.com/zenml-io/zenml", + website_url: str = "https://zenml.io", +) -> Annotated[List[str], "urls"]: + """Generates a list of relevant URLs to scrape. + + Args: + docs_url: URL to the documentation. + repo_url: URL to the repository. + release_notes_url: URL to the release notes. + website_url: URL to the website. + + Returns: + List of URLs to scrape. + """ + + # We comment this out to make this pipeline faster + # examples_readme_urls = get_nested_readme_urls(repo_url) + docs_urls = get_all_pages(docs_url) + # website_urls = get_all_pages(website_url) + # all_urls = docs_urls + website_urls + examples_readme_urls + all_urls = docs_urls + log_artifact_metadata( + metadata={ + "count": len(all_urls), + }, + ) + return all_urls diff --git a/llm-complete-guide/steps/url_scraping_utils.py b/llm-complete-guide/steps/url_scraping_utils.py new file mode 100644 index 00000000..642fed9c --- /dev/null +++ b/llm-complete-guide/steps/url_scraping_utils.py @@ -0,0 +1,175 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from functools import lru_cache +from logging import getLogger +from time import sleep +from typing import List, Set, Tuple +from urllib.parse import urljoin, urlparse + +import requests +from bs4 import BeautifulSoup +from ratelimit import limits, sleep_and_retry + +logger = getLogger(__name__) + +RATE_LIMIT = 5 # Maximum number of requests per second + + +def is_valid_url(url: str, base: str) -> bool: + """ + Check if the given URL is valid and has the same base as the provided base. + + Args: + url (str): The URL to check. + base (str): The base URL to compare against. + + Returns: + bool: True if the URL is valid and has the same base, False otherwise. + """ + parsed = urlparse(url) + return bool(parsed.netloc) and parsed.netloc == base + + +@sleep_and_retry +@limits(calls=RATE_LIMIT, period=1) +@lru_cache(maxsize=128) +def get_all_links(url: str, base: str) -> List[str]: + """ + Retrieve all valid links from a given URL with the same base. + + Args: + url (str): The URL to retrieve links from. + base (str): The base URL to compare against. + + Returns: + List[str]: A list of valid links with the same base. + """ + logger.debug(f"Retrieving links from {url}") + response = requests.get(url) + soup = BeautifulSoup(response.text, "html.parser") + links = [] + + for link in soup.find_all("a", href=True): + href = link["href"] + full_url = urljoin(url, href) + parsed_url = urlparse(full_url) + cleaned_url = parsed_url._replace(fragment="").geturl() + if is_valid_url(cleaned_url, base): + links.append(cleaned_url) + + logger.debug(f"Found {len(links)} valid links from {url}") + return links + + +def crawl(url: str, base: str, visited: Set[str] = None) -> Set[str]: + """ + Recursively crawl a URL and its links, retrieving all valid links with the same base. + + Args: + url (str): The URL to crawl. + base (str): The base URL to compare against. + visited (Set[str]): A set of URLs that have been visited. Defaults to None. + + Returns: + Set[str]: A set of all valid links with the same base. + """ + if visited is None: + visited = set() + + visited.add(url) + links = get_all_links(url, base) + + for link in links: + if link not in visited: + visited.update(crawl(link, base, visited)) + sleep(1 / RATE_LIMIT) # Rate limit the recursive calls + + return visited + + +def get_all_pages(url: str) -> List[str]: + """ + Retrieve all pages with the same base as the given URL. + + Args: + url (str): The URL to retrieve pages from. + + Returns: + List[str]: A list of all pages with the same base. + """ + logger.info(f"Scraping all pages from {url}...") + base_url = urlparse(url).netloc + pages = crawl(url, base_url) + logger.info(f"Found {len(pages)} pages.") + logger.info("Done scraping pages.") + return list(pages) + + +@sleep_and_retry +@limits(calls=RATE_LIMIT, period=1) +@lru_cache(maxsize=128) +def get_readme_urls(repo_url: str) -> Tuple[List[str], List[str]]: + """ + Retrieve folder and README links from a GitHub repository. + + Args: + repo_url (str): The URL of the GitHub repository. + + Returns: + Tuple[List[str], List[str]]: A tuple containing two lists: folder links and README links. + """ + logger.debug(f"Retrieving README links from {repo_url}") + headers = {"Accept": "application/vnd.github+json"} + r = requests.get(repo_url, headers=headers) + soup = BeautifulSoup(r.text, "html.parser") + + folder_links = [] + readme_links = [] + + for link in soup.find_all("a", class_="js-navigation-open Link--primary"): + href = link["href"] + full_url = f"https://github.com{href}" + if "tree" in href: + folder_links.append(full_url) + elif "README.md" in href: + readme_links.append(full_url) + + logger.debug( + f"Found {len(folder_links)} folder links and {len(readme_links)} README links from {repo_url}" + ) + return folder_links, readme_links + + +def get_nested_readme_urls(repo_url: str) -> List[str]: + """ + Retrieve all nested README links from a GitHub repository. + + Args: + repo_url (str): The URL of the GitHub repository. + + Returns: + List[str]: A list of all nested README links. + """ + logger.info(f"Retrieving nested README links from {repo_url}...") + folder_links, readme_links = get_readme_urls(repo_url) + + for folder_link in folder_links: + _, nested_readme_links = get_readme_urls(folder_link) + readme_links.extend(nested_readme_links) + + logger.info( + f"Found {len(readme_links)} nested README links from {repo_url}" + ) + return readme_links diff --git a/llm-complete-guide/steps/web_url_loader.py b/llm-complete-guide/steps/web_url_loader.py new file mode 100644 index 00000000..7b95e751 --- /dev/null +++ b/llm-complete-guide/steps/web_url_loader.py @@ -0,0 +1,35 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from typing import List + +from langchain.docstore.document import Document +from langchain_community.document_loaders import UnstructuredURLLoader +from zenml import step + + +@step +def web_url_loader(urls: List[str]) -> List[Document]: + """Loads documents from a list of URLs. + + Args: + urls: List of URLs to load documents from. + + Returns: + List of langchain documents. + """ + loader = UnstructuredURLLoader( + urls=urls, + ) + return loader.load() From 603c5978427b9585319ca484b29d0a5e1cb173e1 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 15:30:28 +0100 Subject: [PATCH 02/32] add indexing functionality --- llm-complete-guide/pipelines/llm_basic_rag.py | 10 +- llm-complete-guide/requirements.txt | 13 +- llm-complete-guide/steps/populate_index.py | 134 ++++++++++++++++++ llm-complete-guide/steps/url_scraper.py | 34 +++-- 4 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 llm-complete-guide/steps/populate_index.py diff --git a/llm-complete-guide/pipelines/llm_basic_rag.py b/llm-complete-guide/pipelines/llm_basic_rag.py index 7b94317d..2558da43 100644 --- a/llm-complete-guide/pipelines/llm_basic_rag.py +++ b/llm-complete-guide/pipelines/llm_basic_rag.py @@ -1,6 +1,11 @@ from zenml import pipeline from steps.url_scraper import url_scraper from steps.web_url_loader import web_url_loader +from steps.populate_index import ( + preprocess_documents, + generate_embeddings, + index_generator, +) @pipeline @@ -8,4 +13,7 @@ def llm_basic_rag() -> None: """Pipeline to train a basic RAG model.""" urls = url_scraper() - web_url_loader(urls=urls) + docs = web_url_loader(urls=urls) + processed_docs = preprocess_documents(documents=docs) + embeddings = generate_embeddings(split_documents=processed_docs) + index_generator(embeddings=embeddings, documents=docs) diff --git a/llm-complete-guide/requirements.txt b/llm-complete-guide/requirements.txt index b6f4853e..c3842b25 100644 --- a/llm-complete-guide/requirements.txt +++ b/llm-complete-guide/requirements.txt @@ -1,3 +1,14 @@ -# zenml +zenml langchain-community ratelimit +langchain<0.1.0 +langchain-openai +pgvector +psycopg2-binary +beautifulsoup4 +unstructured +pandas +numpy +# tiktoken +# sentence-transformers +nomic diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py new file mode 100644 index 00000000..a7306554 --- /dev/null +++ b/llm-complete-guide/steps/populate_index.py @@ -0,0 +1,134 @@ +from typing import Annotated, List + +from zenml import ArtifactConfig, step, log_artifact_metadata +from langchain.docstore.document import Document +from langchain_text_splitters import CharacterTextSplitter +from zenml.client import Client +from sentence_transformers import SentenceTransformer +import numpy as np +import psycopg2 +from pgvector.psycopg2 import register_vector +import math + +EMBEDDINGS_MODEL = "all-distilroberta-v1" +CHUNK_SIZE = 512 +CHUNK_OVERLAP = 50 +EMBEDDING_DIMENSIONALITY = ( + 768 # Update this to match the dimensionality of the new model +) + + +@step(enable_cache=False) +def preprocess_documents( + documents: List[Document], +) -> Annotated[List[Document], ArtifactConfig(name="split_document_chunks")]: + log_artifact_metadata( + artifact_name="split_document_chunks", + metadata={ + "chunk_size": CHUNK_SIZE, + "chunk_overlap": CHUNK_OVERLAP, + }, + ) + text_splitter = CharacterTextSplitter( + chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP + ) + return text_splitter.split_documents(documents) + + +@step(enable_cache=False) +def generate_embeddings( + split_documents: List[Document], +) -> Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: + model = SentenceTransformer(EMBEDDINGS_MODEL) + + log_artifact_metadata( + artifact_name="embeddings", + metadata={ + "embedding_type": EMBEDDINGS_MODEL, + "embedding_dimensionality": EMBEDDING_DIMENSIONALITY, + }, + ) + raw_texts = [doc.page_content for doc in split_documents] + return model.encode(raw_texts) + + +@step(enable_cache=False) +def index_generator( + embeddings: np.ndarray, + documents: List[Document], +) -> None: + pg_password = Client().get_secret("postgres_db").secret_values["password"] + + CONNECTION_DETAILS = { + "user": "postgres.jjpynzoqhdifcfroyfon", + "password": pg_password, + "host": "aws-0-eu-central-1.pooler.supabase.com", + "port": "5432", + "dbname": "postgres", + } + + conn = psycopg2.connect(**CONNECTION_DETAILS) + cur = conn.cursor() + + # Install pgvector if not already installed + cur.execute("CREATE EXTENSION IF NOT EXISTS vector") + conn.commit() + + # Create the embeddings table if it doesn't exist + table_create_command = f""" + CREATE TABLE IF NOT EXISTS embeddings ( + id SERIAL PRIMARY KEY, + title TEXT, + url TEXT, + content TEXT, + tokens INTEGER, + embedding VECTOR({EMBEDDING_DIMENSIONALITY}) + ); + """ + cur.execute(table_create_command) + conn.commit() + + register_vector(conn) + + # Prepare the list of tuples to insert + data_list = [] + for i, doc in enumerate(documents): + title = doc.metadata.get("title", "") + url = doc.metadata.get("url", "") + content = doc.page_content + tokens = len( + content.split() + ) # Approximate token count based on word count + embedding = embeddings[i].tolist() + data_list.append((title, url, content, tokens, embedding)) + + # Insert data only if it doesn't already exist + cur.execute("SELECT COUNT(*) FROM embeddings") + count = cur.fetchone()[0] + if count == 0: + psycopg2.extras.execute_values( + cur, + "INSERT INTO embeddings (title, url, content, tokens, embedding) VALUES %s", + data_list, + template="(%s, %s, %s, %s, %s)", + ) + conn.commit() + + cur.execute("SELECT COUNT(*) as cnt FROM embeddings;") + num_records = cur.fetchone()[0] + print("Number of vector records in table: ", num_records, "\n") + + # calculate the index parameters according to best practices + num_lists = num_records / 1000 + num_lists = max(num_lists, 10) + if num_records > 1000000: + num_lists = math.sqrt(num_records) + + # use the cosine distance measure, which is what we'll later use for querying + cur.execute( + f"CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = {num_lists});" + ) + conn.commit() + + cur.close() + conn.close() diff --git a/llm-complete-guide/steps/url_scraper.py b/llm-complete-guide/steps/url_scraper.py index 000ef8cd..7d0cb791 100644 --- a/llm-complete-guide/steps/url_scraper.py +++ b/llm-complete-guide/steps/url_scraper.py @@ -14,8 +14,7 @@ from typing import List from typing_extensions import Annotated -from steps.url_scraping_utils import get_all_pages, get_nested_readme_urls -from zenml import step, log_artifact_metadata +from zenml import step @step @@ -35,16 +34,21 @@ def url_scraper( Returns: List of URLs to scrape. """ - - # We comment this out to make this pipeline faster - # examples_readme_urls = get_nested_readme_urls(repo_url) - docs_urls = get_all_pages(docs_url) - # website_urls = get_all_pages(website_url) - # all_urls = docs_urls + website_urls + examples_readme_urls - all_urls = docs_urls - log_artifact_metadata( - metadata={ - "count": len(all_urls), - }, - ) - return all_urls + # # We comment this out to make this pipeline faster + # # examples_readme_urls = get_nested_readme_urls(repo_url) + # docs_urls = get_all_pages(docs_url) + # # website_urls = get_all_pages(website_url) + # # all_urls = docs_urls + website_urls + examples_readme_urls + # all_urls = docs_urls + # log_artifact_metadata( + # metadata={ + # "count": len(all_urls), + # }, + # ) + # return all_urls + # TODO: revert once url scraping is fixed + return [ + docs_url, + "https://docs.zenml.io/getting-started/installation", + "https://docs.zenml.io/getting-started/core-concepts", + ] From 05b488b36c2b8c7a97afb7c253e0d8ba0c45ba81 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 16:03:10 +0100 Subject: [PATCH 03/32] finish basic pipeline functionality --- llm-complete-guide/pipelines/llm_basic_rag.py | 1 + llm-complete-guide/run.py | 14 +++ llm-complete-guide/steps/populate_index.py | 16 +--- llm-complete-guide/utils/__init__.py | 0 llm-complete-guide/utils/llm_utils.py | 95 +++++++++++++++++++ 5 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 llm-complete-guide/utils/__init__.py create mode 100644 llm-complete-guide/utils/llm_utils.py diff --git a/llm-complete-guide/pipelines/llm_basic_rag.py b/llm-complete-guide/pipelines/llm_basic_rag.py index 2558da43..1357018c 100644 --- a/llm-complete-guide/pipelines/llm_basic_rag.py +++ b/llm-complete-guide/pipelines/llm_basic_rag.py @@ -17,3 +17,4 @@ def llm_basic_rag() -> None: processed_docs = preprocess_documents(documents=docs) embeddings = generate_embeddings(split_documents=processed_docs) index_generator(embeddings=embeddings, documents=docs) + diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py index dc5e1930..d3668827 100644 --- a/llm-complete-guide/run.py +++ b/llm-complete-guide/run.py @@ -24,6 +24,7 @@ from pipelines import ( llm_basic_rag, ) +from utils.llm_utils import process_input_with_retrieval logger = get_logger(__name__) @@ -48,8 +49,16 @@ default=False, help="Whether to run the pipeline that creates the dataset.", ) +@click.option( + "--rag-query", + "rag_query", + type=str, + required=False, + help="Query the RAG model.", +) def main( basic_rag: bool = False, + rag_query: Optional[str] = None, no_cache: bool = False, ): """Main entry point for the pipeline execution. @@ -59,6 +68,11 @@ def main( """ pipeline_args = {"enable_cache": not no_cache} + if rag_query: + # query the llm + response = process_input_with_retrieval(rag_query) + print(response) + if basic_rag: llm_basic_rag.with_options(**pipeline_args)() diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index a7306554..a19eabb0 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -3,15 +3,15 @@ from zenml import ArtifactConfig, step, log_artifact_metadata from langchain.docstore.document import Document from langchain_text_splitters import CharacterTextSplitter -from zenml.client import Client from sentence_transformers import SentenceTransformer import numpy as np import psycopg2 from pgvector.psycopg2 import register_vector import math +from utils.llm_utils import get_db_conn EMBEDDINGS_MODEL = "all-distilroberta-v1" -CHUNK_SIZE = 512 +CHUNK_SIZE = 256 CHUNK_OVERLAP = 50 EMBEDDING_DIMENSIONALITY = ( 768 # Update this to match the dimensionality of the new model @@ -57,17 +57,7 @@ def index_generator( embeddings: np.ndarray, documents: List[Document], ) -> None: - pg_password = Client().get_secret("postgres_db").secret_values["password"] - - CONNECTION_DETAILS = { - "user": "postgres.jjpynzoqhdifcfroyfon", - "password": pg_password, - "host": "aws-0-eu-central-1.pooler.supabase.com", - "port": "5432", - "dbname": "postgres", - } - - conn = psycopg2.connect(**CONNECTION_DETAILS) + conn = get_db_conn() cur = conn.cursor() # Install pgvector if not already installed diff --git a/llm-complete-guide/utils/__init__.py b/llm-complete-guide/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py new file mode 100644 index 00000000..1fa70214 --- /dev/null +++ b/llm-complete-guide/utils/llm_utils.py @@ -0,0 +1,95 @@ +import numpy as np +import psycopg2 +from sentence_transformers import SentenceTransformer +from pgvector.psycopg2 import register_vector +from openai import OpenAI +from zenml.client import Client +from psycopg2.extensions import connection + +OPENAI_MODEL = "gpt-3.5-turbo" +EMBEDDINGS_MODEL = "all-distilroberta-v1" + +openai_client = OpenAI() + + +def get_db_conn() -> connection: + pg_password = Client().get_secret("postgres_db").secret_values["password"] + + CONNECTION_DETAILS = { + "user": "postgres.jjpynzoqhdifcfroyfon", + "password": pg_password, + "host": "aws-0-eu-central-1.pooler.supabase.com", + "port": "5432", + "dbname": "postgres", + } + + return psycopg2.connect(**CONNECTION_DETAILS) + + +def get_topn_similar_docs(query_embedding, conn, n: int = 5): + embedding_array = np.array(query_embedding) + register_vector(conn) + cur = conn.cursor() + # Get the top n most similar documents using the KNN <=> operator + cur.execute( + f"SELECT content FROM embeddings ORDER BY embedding <=> %s LIMIT {n}", + (embedding_array,), + ) + return cur.fetchall() + + +def get_completion_from_messages( + messages, model=OPENAI_MODEL, temperature=0, max_tokens=1000 +): + completion = openai_client.chat.completions.create( + model=model, + messages=messages, + temperature=temperature, + max_tokens=max_tokens, + ) + return completion.choices[0].message.content + + +# Helper function: get embeddings for a text +def get_embeddings(text): + model = SentenceTransformer(EMBEDDINGS_MODEL) + return model.encode(text) + + +def process_input_with_retrieval(input: str) -> str: + """Process the input with retrieval. + + Args: + input (str): The input to process. + + Returns: + str: The processed output. + """ + delimiter = "```" + + # Step 1: Get documents related to the user input from database + related_docs = get_topn_similar_docs(get_embeddings(input), get_db_conn()) + + # Step 2: Get completion from OpenAI API + # Set system message to help set appropriate tone and context for model + system_message = f""" + You are a friendly chatbot. \ + You can answer questions about ZenML, its features and its use cases. \ + You respond in a concise, technically credible tone. \ + """ + + # Prepare messages to pass to model + # We use a delimiter to help the model understand the where the user_input + # starts and ends + + messages = [ + {"role": "system", "content": system_message}, + {"role": "user", "content": f"{delimiter}{input}{delimiter}"}, + { + "role": "assistant", + "content": f"Relevant ZenML documentation: \n" + + "\n".join(doc[0] for doc in related_docs), + }, + ] + + return get_completion_from_messages(messages) From 9c50f803df1cc3575ad3177baa91cee3ce385e8d Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 16:25:05 +0100 Subject: [PATCH 04/32] update llm_utils and format --- llm-complete-guide/pipelines/llm_basic_rag.py | 9 ++++----- llm-complete-guide/run.py | 4 +--- llm-complete-guide/steps/populate_index.py | 10 +++++----- llm-complete-guide/steps/url_scraper.py | 1 + llm-complete-guide/utils/llm_utils.py | 6 +++--- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/llm-complete-guide/pipelines/llm_basic_rag.py b/llm-complete-guide/pipelines/llm_basic_rag.py index 1357018c..7dbe88c4 100644 --- a/llm-complete-guide/pipelines/llm_basic_rag.py +++ b/llm-complete-guide/pipelines/llm_basic_rag.py @@ -1,11 +1,11 @@ -from zenml import pipeline -from steps.url_scraper import url_scraper -from steps.web_url_loader import web_url_loader from steps.populate_index import ( - preprocess_documents, generate_embeddings, index_generator, + preprocess_documents, ) +from steps.url_scraper import url_scraper +from steps.web_url_loader import web_url_loader +from zenml import pipeline @pipeline @@ -17,4 +17,3 @@ def llm_basic_rag() -> None: processed_docs = preprocess_documents(documents=docs) embeddings = generate_embeddings(split_documents=processed_docs) index_generator(embeddings=embeddings, documents=docs) - diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py index d3668827..691fd838 100644 --- a/llm-complete-guide/run.py +++ b/llm-complete-guide/run.py @@ -15,16 +15,14 @@ # limitations under the License. # -import os from typing import Optional import click -from zenml.logger import get_logger - from pipelines import ( llm_basic_rag, ) from utils.llm_utils import process_input_with_retrieval +from zenml.logger import get_logger logger = get_logger(__name__) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index a19eabb0..6f8ba481 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -1,14 +1,14 @@ +import math from typing import Annotated, List -from zenml import ArtifactConfig, step, log_artifact_metadata -from langchain.docstore.document import Document -from langchain_text_splitters import CharacterTextSplitter -from sentence_transformers import SentenceTransformer import numpy as np import psycopg2 +from langchain.docstore.document import Document +from langchain_text_splitters import CharacterTextSplitter from pgvector.psycopg2 import register_vector -import math +from sentence_transformers import SentenceTransformer from utils.llm_utils import get_db_conn +from zenml import ArtifactConfig, log_artifact_metadata, step EMBEDDINGS_MODEL = "all-distilroberta-v1" CHUNK_SIZE = 256 diff --git a/llm-complete-guide/steps/url_scraper.py b/llm-complete-guide/steps/url_scraper.py index 7d0cb791..f3e3dd20 100644 --- a/llm-complete-guide/steps/url_scraper.py +++ b/llm-complete-guide/steps/url_scraper.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. from typing import List + from typing_extensions import Annotated from zenml import step diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 1fa70214..506302b9 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -1,10 +1,10 @@ import numpy as np import psycopg2 -from sentence_transformers import SentenceTransformer -from pgvector.psycopg2 import register_vector from openai import OpenAI -from zenml.client import Client +from pgvector.psycopg2 import register_vector from psycopg2.extensions import connection +from sentence_transformers import SentenceTransformer +from zenml.client import Client OPENAI_MODEL = "gpt-3.5-turbo" EMBEDDINGS_MODEL = "all-distilroberta-v1" From c85da662c5fb61dcca5bae6e7eec836c290b6822 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 17:02:36 +0100 Subject: [PATCH 05/32] refactored the url scraper + utils --- llm-complete-guide/steps/url_scraper.py | 40 +++++----- .../steps/url_scraping_utils.py | 80 ++++++++++--------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/llm-complete-guide/steps/url_scraper.py b/llm-complete-guide/steps/url_scraper.py index f3e3dd20..bd015643 100644 --- a/llm-complete-guide/steps/url_scraper.py +++ b/llm-complete-guide/steps/url_scraper.py @@ -15,7 +15,9 @@ from typing import List from typing_extensions import Annotated -from zenml import step +from zenml import log_artifact_metadata, step + +from steps.url_scraping_utils import get_all_pages @step @@ -35,21 +37,21 @@ def url_scraper( Returns: List of URLs to scrape. """ - # # We comment this out to make this pipeline faster - # # examples_readme_urls = get_nested_readme_urls(repo_url) - # docs_urls = get_all_pages(docs_url) - # # website_urls = get_all_pages(website_url) - # # all_urls = docs_urls + website_urls + examples_readme_urls - # all_urls = docs_urls - # log_artifact_metadata( - # metadata={ - # "count": len(all_urls), - # }, - # ) - # return all_urls - # TODO: revert once url scraping is fixed - return [ - docs_url, - "https://docs.zenml.io/getting-started/installation", - "https://docs.zenml.io/getting-started/core-concepts", - ] + # We comment this out to make this pipeline faster + # examples_readme_urls = get_nested_readme_urls(repo_url) + docs_urls = get_all_pages(docs_url) + # website_urls = get_all_pages(website_url) + # all_urls = docs_urls + website_urls + examples_readme_urls + all_urls = docs_urls + log_artifact_metadata( + metadata={ + "count": len(all_urls), + }, + ) + return all_urls + # # TODO: revert once url scraping is fixed + # return [ + # docs_url, + # "https://docs.zenml.io/getting-started/installation", + # "https://docs.zenml.io/getting-started/core-concepts", + # ] diff --git a/llm-complete-guide/steps/url_scraping_utils.py b/llm-complete-guide/steps/url_scraping_utils.py index 642fed9c..426f8026 100644 --- a/llm-complete-guide/steps/url_scraping_utils.py +++ b/llm-complete-guide/steps/url_scraping_utils.py @@ -12,6 +12,7 @@ # or implied. See the License for the specific language governing # permissions and limitations under the License. +import re from functools import lru_cache from logging import getLogger from time import sleep @@ -29,48 +30,41 @@ def is_valid_url(url: str, base: str) -> bool: """ - Check if the given URL is valid and has the same base as the provided base. + Check if the given URL is valid, has the same base as the provided base, + and does not contain any version-specific paths. Args: url (str): The URL to check. base (str): The base URL to compare against. Returns: - bool: True if the URL is valid and has the same base, False otherwise. + bool: True if the URL is valid, has the same base, and does not contain version-specific paths, False otherwise. """ parsed = urlparse(url) - return bool(parsed.netloc) and parsed.netloc == base + if not bool(parsed.netloc) or parsed.netloc != base: + return False + # Check if the URL contains a version pattern (e.g., /v/0.x.x/) + version_pattern = r"/v/0\.\d+\.\d+/" + return not re.search(version_pattern, url) -@sleep_and_retry -@limits(calls=RATE_LIMIT, period=1) -@lru_cache(maxsize=128) -def get_all_links(url: str, base: str) -> List[str]: + +def get_all_pages(url: str) -> List[str]: """ - Retrieve all valid links from a given URL with the same base. + Retrieve all pages with the same base as the given URL. Args: - url (str): The URL to retrieve links from. - base (str): The base URL to compare against. + url (str): The URL to retrieve pages from. Returns: - List[str]: A list of valid links with the same base. + List[str]: A list of all pages with the same base. """ - logger.debug(f"Retrieving links from {url}") - response = requests.get(url) - soup = BeautifulSoup(response.text, "html.parser") - links = [] - - for link in soup.find_all("a", href=True): - href = link["href"] - full_url = urljoin(url, href) - parsed_url = urlparse(full_url) - cleaned_url = parsed_url._replace(fragment="").geturl() - if is_valid_url(cleaned_url, base): - links.append(cleaned_url) - - logger.debug(f"Found {len(links)} valid links from {url}") - return links + logger.info(f"Scraping all pages from {url}...") + base_url = urlparse(url).netloc + pages = crawl(url, base_url) + logger.info(f"Found {len(pages)} pages.") + logger.info("Done scraping pages.") + return list(pages) def crawl(url: str, base: str, visited: Set[str] = None) -> Set[str]: @@ -89,6 +83,7 @@ def crawl(url: str, base: str, visited: Set[str] = None) -> Set[str]: visited = set() visited.add(url) + logger.debug(f"Crawling URL: {url}") links = get_all_links(url, base) for link in links: @@ -99,22 +94,35 @@ def crawl(url: str, base: str, visited: Set[str] = None) -> Set[str]: return visited -def get_all_pages(url: str) -> List[str]: +@sleep_and_retry +@limits(calls=RATE_LIMIT, period=1) +@lru_cache(maxsize=128) +def get_all_links(url: str, base: str) -> List[str]: """ - Retrieve all pages with the same base as the given URL. + Retrieve all valid links from a given URL with the same base. Args: - url (str): The URL to retrieve pages from. + url (str): The URL to retrieve links from. + base (str): The base URL to compare against. Returns: - List[str]: A list of all pages with the same base. + List[str]: A list of valid links with the same base. """ - logger.info(f"Scraping all pages from {url}...") - base_url = urlparse(url).netloc - pages = crawl(url, base_url) - logger.info(f"Found {len(pages)} pages.") - logger.info("Done scraping pages.") - return list(pages) + logger.debug(f"Retrieving links from {url}") + response = requests.get(url) + soup = BeautifulSoup(response.text, "html.parser") + links = [] + + for link in soup.find_all("a", href=True): + href = link["href"] + full_url = urljoin(url, href) + parsed_url = urlparse(full_url) + cleaned_url = parsed_url._replace(fragment="").geturl() + if is_valid_url(cleaned_url, base): + links.append(cleaned_url) + + logger.debug(f"Found {len(links)} valid links from {url}") + return links @sleep_and_retry From 362c5723d739d2dd9b34cc7a0cfdcdd019c39b34 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 17:53:31 +0100 Subject: [PATCH 06/32] refactoring part 2 --- llm-complete-guide/README.md | 4 ++++ llm-complete-guide/constants.py | 14 ++++++++++++++ llm-complete-guide/steps/populate_index.py | 15 ++++++++------- llm-complete-guide/steps/url_scraper.py | 6 ------ llm-complete-guide/steps/url_scraping_utils.py | 4 ++-- llm-complete-guide/utils/llm_utils.py | 3 +-- 6 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 llm-complete-guide/constants.py diff --git a/llm-complete-guide/README.md b/llm-complete-guide/README.md index 94267385..38ae1ce1 100644 --- a/llm-complete-guide/README.md +++ b/llm-complete-guide/README.md @@ -1,3 +1,7 @@ +# export your OpenAI key +# SUPABASE credentials? + + # ☮️ Fine-tuning open source LLMs using MLOps pipelines Welcome to your newly generated "ZenML LLM Finetuning project" project! This is diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py new file mode 100644 index 00000000..a6fb7a11 --- /dev/null +++ b/llm-complete-guide/constants.py @@ -0,0 +1,14 @@ +# Vector Store constants +EMBEDDINGS_MODEL = "all-distilroberta-v1" +CHUNK_SIZE = 256 +CHUNK_OVERLAP = 50 +EMBEDDING_DIMENSIONALITY = ( + 768 # Update this to match the dimensionality of the new model +) + +# Scraping constants +RATE_LIMIT = 5 # Maximum number of requests per second + +# LLM Utils constants +OPENAI_MODEL = "gpt-3.5-turbo" +EMBEDDINGS_MODEL = "all-distilroberta-v1" diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index 6f8ba481..fc07a96e 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -10,15 +10,16 @@ from utils.llm_utils import get_db_conn from zenml import ArtifactConfig, log_artifact_metadata, step -EMBEDDINGS_MODEL = "all-distilroberta-v1" -CHUNK_SIZE = 256 -CHUNK_OVERLAP = 50 -EMBEDDING_DIMENSIONALITY = ( - 768 # Update this to match the dimensionality of the new model + +from constants import ( + CHUNK_OVERLAP, + CHUNK_SIZE, + EMBEDDING_DIMENSIONALITY, + EMBEDDINGS_MODEL, ) -@step(enable_cache=False) +@step def preprocess_documents( documents: List[Document], ) -> Annotated[List[Document], ArtifactConfig(name="split_document_chunks")]: @@ -35,7 +36,7 @@ def preprocess_documents( return text_splitter.split_documents(documents) -@step(enable_cache=False) +@step def generate_embeddings( split_documents: List[Document], ) -> Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: diff --git a/llm-complete-guide/steps/url_scraper.py b/llm-complete-guide/steps/url_scraper.py index bd015643..213c6994 100644 --- a/llm-complete-guide/steps/url_scraper.py +++ b/llm-complete-guide/steps/url_scraper.py @@ -49,9 +49,3 @@ def url_scraper( }, ) return all_urls - # # TODO: revert once url scraping is fixed - # return [ - # docs_url, - # "https://docs.zenml.io/getting-started/installation", - # "https://docs.zenml.io/getting-started/core-concepts", - # ] diff --git a/llm-complete-guide/steps/url_scraping_utils.py b/llm-complete-guide/steps/url_scraping_utils.py index 426f8026..f400761b 100644 --- a/llm-complete-guide/steps/url_scraping_utils.py +++ b/llm-complete-guide/steps/url_scraping_utils.py @@ -23,9 +23,9 @@ from bs4 import BeautifulSoup from ratelimit import limits, sleep_and_retry -logger = getLogger(__name__) +from constants import RATE_LIMIT -RATE_LIMIT = 5 # Maximum number of requests per second +logger = getLogger(__name__) def is_valid_url(url: str, base: str) -> bool: diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 506302b9..1c3243d5 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -6,8 +6,7 @@ from sentence_transformers import SentenceTransformer from zenml.client import Client -OPENAI_MODEL = "gpt-3.5-turbo" -EMBEDDINGS_MODEL = "all-distilroberta-v1" +from constants import EMBEDDINGS_MODEL, OPENAI_MODEL openai_client = OpenAI() From 998d59304323c789befd6399a399c4a633f953a9 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 17:57:40 +0100 Subject: [PATCH 07/32] fix DB update functionality --- llm-complete-guide/steps/populate_index.py | 23 ++++++++++------------ 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index fc07a96e..7397d34e 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -81,8 +81,7 @@ def index_generator( register_vector(conn) - # Prepare the list of tuples to insert - data_list = [] + # Insert data only if it doesn't already exist for i, doc in enumerate(documents): title = doc.metadata.get("title", "") url = doc.metadata.get("url", "") @@ -91,19 +90,17 @@ def index_generator( content.split() ) # Approximate token count based on word count embedding = embeddings[i].tolist() - data_list.append((title, url, content, tokens, embedding)) - # Insert data only if it doesn't already exist - cur.execute("SELECT COUNT(*) FROM embeddings") - count = cur.fetchone()[0] - if count == 0: - psycopg2.extras.execute_values( - cur, - "INSERT INTO embeddings (title, url, content, tokens, embedding) VALUES %s", - data_list, - template="(%s, %s, %s, %s, %s)", + cur.execute( + "SELECT COUNT(*) FROM embeddings WHERE content = %s", (content,) ) - conn.commit() + count = cur.fetchone()[0] + if count == 0: + cur.execute( + "INSERT INTO embeddings (title, url, content, tokens, embedding) VALUES (%s, %s, %s, %s, %s)", + (title, url, content, tokens, embedding), + ) + conn.commit() cur.execute("SELECT COUNT(*) as cnt FROM embeddings;") num_records = cur.fetchone()[0] From 35cf8c4b8c2ab1b08af83c6be84564423c63035f Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 19:39:00 +0100 Subject: [PATCH 08/32] add option to switch out the llm within the CLI --- llm-complete-guide/constants.py | 3 ++- llm-complete-guide/requirements.txt | 7 +++--- llm-complete-guide/run.py | 12 +++++++++- llm-complete-guide/steps/populate_index.py | 15 +++++-------- .../steps/url_scraping_utils.py | 3 +-- llm-complete-guide/utils/llm_utils.py | 22 ++++++++++++++----- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py index a6fb7a11..2d69c6ae 100644 --- a/llm-complete-guide/constants.py +++ b/llm-complete-guide/constants.py @@ -10,5 +10,6 @@ RATE_LIMIT = 5 # Maximum number of requests per second # LLM Utils constants -OPENAI_MODEL = "gpt-3.5-turbo" +OPENAI_MODEL = "gpt-4-0125-preview" +# OPENAI_MODEL = "gpt-3.5-turbo" EMBEDDINGS_MODEL = "all-distilroberta-v1" diff --git a/llm-complete-guide/requirements.txt b/llm-complete-guide/requirements.txt index c3842b25..2fab8547 100644 --- a/llm-complete-guide/requirements.txt +++ b/llm-complete-guide/requirements.txt @@ -1,7 +1,7 @@ zenml langchain-community ratelimit -langchain<0.1.0 +langchain>=0.0.325 langchain-openai pgvector psycopg2-binary @@ -9,6 +9,5 @@ beautifulsoup4 unstructured pandas numpy -# tiktoken -# sentence-transformers -nomic +sentence-transformers +litellm diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py index 691fd838..0430b7b2 100644 --- a/llm-complete-guide/run.py +++ b/llm-complete-guide/run.py @@ -18,6 +18,7 @@ from typing import Optional import click +from constants import OPENAI_MODEL from pipelines import ( llm_basic_rag, ) @@ -54,9 +55,18 @@ required=False, help="Query the RAG model.", ) +@click.option( + "--model", + "model", + type=click.Choice(["gpt4", "gpt35"]), + required=False, + default="gpt4", + help="The model to use for the completion.", +) def main( basic_rag: bool = False, rag_query: Optional[str] = None, + model: str = OPENAI_MODEL, no_cache: bool = False, ): """Main entry point for the pipeline execution. @@ -68,7 +78,7 @@ def main( if rag_query: # query the llm - response = process_input_with_retrieval(rag_query) + response = process_input_with_retrieval(rag_query, model=model) print(response) if basic_rag: diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index 7397d34e..729ca796 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -2,21 +2,18 @@ from typing import Annotated, List import numpy as np -import psycopg2 -from langchain.docstore.document import Document -from langchain_text_splitters import CharacterTextSplitter -from pgvector.psycopg2 import register_vector -from sentence_transformers import SentenceTransformer -from utils.llm_utils import get_db_conn -from zenml import ArtifactConfig, log_artifact_metadata, step - - from constants import ( CHUNK_OVERLAP, CHUNK_SIZE, EMBEDDING_DIMENSIONALITY, EMBEDDINGS_MODEL, ) +from langchain.docstore.document import Document +from langchain_text_splitters import CharacterTextSplitter +from pgvector.psycopg2 import register_vector +from sentence_transformers import SentenceTransformer +from utils.llm_utils import get_db_conn +from zenml import ArtifactConfig, log_artifact_metadata, step @step diff --git a/llm-complete-guide/steps/url_scraping_utils.py b/llm-complete-guide/steps/url_scraping_utils.py index f400761b..4da721f1 100644 --- a/llm-complete-guide/steps/url_scraping_utils.py +++ b/llm-complete-guide/steps/url_scraping_utils.py @@ -21,9 +21,8 @@ import requests from bs4 import BeautifulSoup -from ratelimit import limits, sleep_and_retry - from constants import RATE_LIMIT +from ratelimit import limits, sleep_and_retry logger = getLogger(__name__) diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 1c3243d5..18a8cd0d 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -1,15 +1,18 @@ +import logging + import numpy as np import psycopg2 +from constants import EMBEDDINGS_MODEL, OPENAI_MODEL from openai import OpenAI from pgvector.psycopg2 import register_vector from psycopg2.extensions import connection from sentence_transformers import SentenceTransformer from zenml.client import Client -from constants import EMBEDDINGS_MODEL, OPENAI_MODEL - openai_client = OpenAI() +logger = logging.getLogger(__name__) + def get_db_conn() -> connection: pg_password = Client().get_secret("postgres_db").secret_values["password"] @@ -38,8 +41,12 @@ def get_topn_similar_docs(query_embedding, conn, n: int = 5): def get_completion_from_messages( - messages, model=OPENAI_MODEL, temperature=0, max_tokens=1000 + messages, model=OPENAI_MODEL, temperature=0.4, max_tokens=1000 ): + if model == "gpt4": + model = "gpt-4-0125-preview" + elif model == "gpt35": + model = "gpt-3.5-turbo" completion = openai_client.chat.completions.create( model=model, messages=messages, @@ -55,7 +62,7 @@ def get_embeddings(text): return model.encode(text) -def process_input_with_retrieval(input: str) -> str: +def process_input_with_retrieval(input: str, model: str = OPENAI_MODEL) -> str: """Process the input with retrieval. Args: @@ -75,6 +82,9 @@ def process_input_with_retrieval(input: str) -> str: You are a friendly chatbot. \ You can answer questions about ZenML, its features and its use cases. \ You respond in a concise, technically credible tone. \ + You ONLY use the context from the ZenML documentation to provide relevant + answers. \ + You do not make up answers or provide opinions that you don't have information to support. \ """ # Prepare messages to pass to model @@ -90,5 +100,5 @@ def process_input_with_retrieval(input: str) -> str: + "\n".join(doc[0] for doc in related_docs), }, ] - - return get_completion_from_messages(messages) + logger.debug("CONTEXT USED\n\n", messages[2]["content"], "\n\n") + return get_completion_from_messages(messages, model=model) From 80e8286ad682f23334143527a274ff1905158012 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 20:03:46 +0100 Subject: [PATCH 09/32] use litellm and drop garbage logs --- llm-complete-guide/run.py | 9 ++++++++- llm-complete-guide/utils/llm_utils.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py index 0430b7b2..2a776214 100644 --- a/llm-complete-guide/run.py +++ b/llm-complete-guide/run.py @@ -58,7 +58,14 @@ @click.option( "--model", "model", - type=click.Choice(["gpt4", "gpt35"]), + type=click.Choice( + [ + "gpt4", + "gpt35", + "claude3", + "claudehaiku", + ] + ), required=False, default="gpt4", help="The model to use for the completion.", diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 18a8cd0d..77347131 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -3,13 +3,14 @@ import numpy as np import psycopg2 from constants import EMBEDDINGS_MODEL, OPENAI_MODEL -from openai import OpenAI +import litellm from pgvector.psycopg2 import register_vector from psycopg2.extensions import connection from sentence_transformers import SentenceTransformer from zenml.client import Client -openai_client = OpenAI() +# Configure the logging level for the root logger +logging.getLogger().setLevel(logging.WARNING) logger = logging.getLogger(__name__) @@ -47,13 +48,17 @@ def get_completion_from_messages( model = "gpt-4-0125-preview" elif model == "gpt35": model = "gpt-3.5-turbo" - completion = openai_client.chat.completions.create( + elif model == "claude3": + model = "claude-3-opus-20240229" + elif model == "claudehaiku": + model = "claude-3-haiku-20240307" + completion_response = litellm.completion( model=model, messages=messages, temperature=temperature, max_tokens=max_tokens, ) - return completion.choices[0].message.content + return completion_response.choices[0].message.content # Helper function: get embeddings for a text From d19ce77dc59a68008d58dc75736381130f874aac Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 20:03:59 +0100 Subject: [PATCH 10/32] formatting --- llm-complete-guide/utils/llm_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 77347131..43736767 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -1,9 +1,9 @@ import logging +import litellm import numpy as np import psycopg2 from constants import EMBEDDINGS_MODEL, OPENAI_MODEL -import litellm from pgvector.psycopg2 import register_vector from psycopg2.extensions import connection from sentence_transformers import SentenceTransformer From 74bf3a824bfb5e69f07ea83f363972643be9232b Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 26 Mar 2024 20:06:06 +0100 Subject: [PATCH 11/32] remove unused title + url --- llm-complete-guide/steps/populate_index.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index 729ca796..ca3007b8 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -66,8 +66,6 @@ def index_generator( table_create_command = f""" CREATE TABLE IF NOT EXISTS embeddings ( id SERIAL PRIMARY KEY, - title TEXT, - url TEXT, content TEXT, tokens INTEGER, embedding VECTOR({EMBEDDING_DIMENSIONALITY}) @@ -80,22 +78,16 @@ def index_generator( # Insert data only if it doesn't already exist for i, doc in enumerate(documents): - title = doc.metadata.get("title", "") - url = doc.metadata.get("url", "") content = doc.page_content - tokens = len( - content.split() - ) # Approximate token count based on word count + tokens = len(content.split()) # Approximate token count based on word count embedding = embeddings[i].tolist() - cur.execute( - "SELECT COUNT(*) FROM embeddings WHERE content = %s", (content,) - ) + cur.execute("SELECT COUNT(*) FROM embeddings WHERE content = %s", (content,)) count = cur.fetchone()[0] if count == 0: cur.execute( - "INSERT INTO embeddings (title, url, content, tokens, embedding) VALUES (%s, %s, %s, %s, %s)", - (title, url, content, tokens, embedding), + "INSERT INTO embeddings (content, tokens, embedding) VALUES (%s, %s, %s)", + (content, tokens, embedding), ) conn.commit() From 642209b2067ef222591c5fd359ed22e1f50aa710 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 07:47:24 +0100 Subject: [PATCH 12/32] rip out langchain completely --- llm-complete-guide/steps/populate_index.py | 32 ++++---- llm-complete-guide/steps/web_url_loader.py | 17 ++-- llm-complete-guide/utils/llm_utils.py | 90 ++++++++++++++++++++++ 3 files changed, 116 insertions(+), 23 deletions(-) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index ca3007b8..3ac3c9a1 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -8,34 +8,31 @@ EMBEDDING_DIMENSIONALITY, EMBEDDINGS_MODEL, ) -from langchain.docstore.document import Document -from langchain_text_splitters import CharacterTextSplitter from pgvector.psycopg2 import register_vector from sentence_transformers import SentenceTransformer -from utils.llm_utils import get_db_conn +from utils.llm_utils import get_db_conn, split_documents from zenml import ArtifactConfig, log_artifact_metadata, step @step def preprocess_documents( - documents: List[Document], -) -> Annotated[List[Document], ArtifactConfig(name="split_document_chunks")]: + documents: List[str], +) -> Annotated[List[str], ArtifactConfig(name="split_chunks")]: log_artifact_metadata( - artifact_name="split_document_chunks", + artifact_name="split_chunks", metadata={ "chunk_size": CHUNK_SIZE, "chunk_overlap": CHUNK_OVERLAP, }, ) - text_splitter = CharacterTextSplitter( - chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP + return split_documents( + documents, chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP ) - return text_splitter.split_documents(documents) @step def generate_embeddings( - split_documents: List[Document], + split_documents: List[str], ) -> Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: model = SentenceTransformer(EMBEDDINGS_MODEL) @@ -46,14 +43,13 @@ def generate_embeddings( "embedding_dimensionality": EMBEDDING_DIMENSIONALITY, }, ) - raw_texts = [doc.page_content for doc in split_documents] - return model.encode(raw_texts) + return model.encode(split_documents) @step(enable_cache=False) def index_generator( embeddings: np.ndarray, - documents: List[Document], + documents: List[str], ) -> None: conn = get_db_conn() cur = conn.cursor() @@ -78,11 +74,15 @@ def index_generator( # Insert data only if it doesn't already exist for i, doc in enumerate(documents): - content = doc.page_content - tokens = len(content.split()) # Approximate token count based on word count + content = doc + tokens = len( + content.split() + ) # Approximate token count based on word count embedding = embeddings[i].tolist() - cur.execute("SELECT COUNT(*) FROM embeddings WHERE content = %s", (content,)) + cur.execute( + "SELECT COUNT(*) FROM embeddings WHERE content = %s", (content,) + ) count = cur.fetchone()[0] if count == 0: cur.execute( diff --git a/llm-complete-guide/steps/web_url_loader.py b/llm-complete-guide/steps/web_url_loader.py index 7b95e751..036cb73a 100644 --- a/llm-complete-guide/steps/web_url_loader.py +++ b/llm-complete-guide/steps/web_url_loader.py @@ -14,13 +14,14 @@ from typing import List -from langchain.docstore.document import Document -from langchain_community.document_loaders import UnstructuredURLLoader +# from langchain.docstore.document import Document +# from langchain_community.document_loaders import UnstructuredURLLoader +from unstructured.partition.html import partition_html from zenml import step @step -def web_url_loader(urls: List[str]) -> List[Document]: +def web_url_loader(urls: List[str]) -> List[str]: """Loads documents from a list of URLs. Args: @@ -29,7 +30,9 @@ def web_url_loader(urls: List[str]) -> List[Document]: Returns: List of langchain documents. """ - loader = UnstructuredURLLoader( - urls=urls, - ) - return loader.load() + document_texts = [] + for url in urls: + elements = partition_html(url="https://python.org/") + text = "\n\n".join([str(el) for el in elements]) + document_texts.append(text) + return document_texts diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 43736767..4d52e34c 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -1,4 +1,6 @@ import logging +import re +from typing import List import litellm import numpy as np @@ -15,6 +17,94 @@ logger = logging.getLogger(__name__) +def split_text_with_regex( + text: str, separator: str, keep_separator: bool +) -> List[str]: + if separator: + if keep_separator: + _splits = re.split(f"({separator})", text) + splits = [ + _splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2) + ] + if len(_splits) % 2 == 0: + splits += _splits[-1:] + splits = [_splits[0]] + splits + else: + splits = re.split(separator, text) + else: + splits = list(text) + return [s for s in splits if s != ""] + + +def split_text( + text: str, + separator: str = "\n\n", + chunk_size: int = 4000, + chunk_overlap: int = 200, + keep_separator: bool = False, + strip_whitespace: bool = True, +) -> List[str]: + if chunk_overlap > chunk_size: + raise ValueError( + f"Got a larger chunk overlap ({chunk_overlap}) than chunk size " + f"({chunk_size}), should be smaller." + ) + + separator_regex = re.escape(separator) + splits = split_text_with_regex(text, separator_regex, keep_separator) + _separator = "" if keep_separator else separator + + chunks = [] + current_chunk = "" + + for split in splits: + if strip_whitespace: + split = split.strip() + + if len(current_chunk) + len(split) + len(_separator) <= chunk_size: + current_chunk += split + _separator + else: + if current_chunk: + chunks.append(current_chunk.rstrip(_separator)) + current_chunk = split + _separator + + if current_chunk: + chunks.append(current_chunk.rstrip(_separator)) + + final_chunks = [] + for i in range(len(chunks)): + if i == 0: + final_chunks.append(chunks[i]) + else: + overlap = chunks[i - 1][-chunk_overlap:] + final_chunks.append(overlap + chunks[i]) + + return final_chunks + + +def split_documents( + documents: List[str], + separator: str = "\n\n", + chunk_size: int = 4000, + chunk_overlap: int = 200, + keep_separator: bool = False, + strip_whitespace: bool = True, +) -> List[str]: + chunked_documents = [] + for doc in documents: + chunked_documents.extend( + split_text( + doc, + separator=separator, + chunk_size=chunk_size, + chunk_overlap=chunk_overlap, + keep_separator=keep_separator, + strip_whitespace=strip_whitespace, + ) + ) + return chunked_documents + + def get_db_conn() -> connection: pg_password = Client().get_secret("postgres_db").secret_values["password"] From d4dd39d63fb7e55bedc1fd438a9ef3fca68fa36c Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 07:49:48 +0100 Subject: [PATCH 13/32] error handling and debug statements --- llm-complete-guide/steps/populate_index.py | 164 +++++++++++---------- 1 file changed, 90 insertions(+), 74 deletions(-) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index 3ac3c9a1..11e61050 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -1,3 +1,4 @@ +import logging import math from typing import Annotated, List @@ -13,37 +14,48 @@ from utils.llm_utils import get_db_conn, split_documents from zenml import ArtifactConfig, log_artifact_metadata, step +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + @step def preprocess_documents( documents: List[str], ) -> Annotated[List[str], ArtifactConfig(name="split_chunks")]: - log_artifact_metadata( - artifact_name="split_chunks", - metadata={ - "chunk_size": CHUNK_SIZE, - "chunk_overlap": CHUNK_OVERLAP, - }, - ) - return split_documents( - documents, chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP - ) + try: + log_artifact_metadata( + artifact_name="split_chunks", + metadata={ + "chunk_size": CHUNK_SIZE, + "chunk_overlap": CHUNK_OVERLAP, + }, + ) + return split_documents( + documents, chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP + ) + except Exception as e: + logger.error(f"Error in preprocess_documents: {e}") + raise @step def generate_embeddings( split_documents: List[str], ) -> Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: - model = SentenceTransformer(EMBEDDINGS_MODEL) - - log_artifact_metadata( - artifact_name="embeddings", - metadata={ - "embedding_type": EMBEDDINGS_MODEL, - "embedding_dimensionality": EMBEDDING_DIMENSIONALITY, - }, - ) - return model.encode(split_documents) + try: + model = SentenceTransformer(EMBEDDINGS_MODEL) + + log_artifact_metadata( + artifact_name="embeddings", + metadata={ + "embedding_type": EMBEDDINGS_MODEL, + "embedding_dimensionality": EMBEDDING_DIMENSIONALITY, + }, + ) + return model.encode(split_documents) + except Exception as e: + logger.error(f"Error in generate_embeddings: {e}") + raise @step(enable_cache=False) @@ -51,61 +63,65 @@ def index_generator( embeddings: np.ndarray, documents: List[str], ) -> None: - conn = get_db_conn() - cur = conn.cursor() - - # Install pgvector if not already installed - cur.execute("CREATE EXTENSION IF NOT EXISTS vector") - conn.commit() - - # Create the embeddings table if it doesn't exist - table_create_command = f""" - CREATE TABLE IF NOT EXISTS embeddings ( - id SERIAL PRIMARY KEY, - content TEXT, - tokens INTEGER, - embedding VECTOR({EMBEDDING_DIMENSIONALITY}) - ); - """ - cur.execute(table_create_command) - conn.commit() - - register_vector(conn) - - # Insert data only if it doesn't already exist - for i, doc in enumerate(documents): - content = doc - tokens = len( - content.split() - ) # Approximate token count based on word count - embedding = embeddings[i].tolist() - - cur.execute( - "SELECT COUNT(*) FROM embeddings WHERE content = %s", (content,) - ) - count = cur.fetchone()[0] - if count == 0: - cur.execute( - "INSERT INTO embeddings (content, tokens, embedding) VALUES (%s, %s, %s)", - (content, tokens, embedding), - ) + try: + conn = get_db_conn() + with conn.cursor() as cur: + # Install pgvector if not already installed + cur.execute("CREATE EXTENSION IF NOT EXISTS vector") conn.commit() - cur.execute("SELECT COUNT(*) as cnt FROM embeddings;") - num_records = cur.fetchone()[0] - print("Number of vector records in table: ", num_records, "\n") - - # calculate the index parameters according to best practices - num_lists = num_records / 1000 - num_lists = max(num_lists, 10) - if num_records > 1000000: - num_lists = math.sqrt(num_records) + # Create the embeddings table if it doesn't exist + table_create_command = f""" + CREATE TABLE IF NOT EXISTS embeddings ( + id SERIAL PRIMARY KEY, + content TEXT, + tokens INTEGER, + embedding VECTOR({EMBEDDING_DIMENSIONALITY}) + ); + """ + cur.execute(table_create_command) + conn.commit() - # use the cosine distance measure, which is what we'll later use for querying - cur.execute( - f"CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = {num_lists});" - ) - conn.commit() + register_vector(conn) + + # Insert data only if it doesn't already exist + for i, doc in enumerate(documents): + content = doc + tokens = len( + content.split() + ) # Approximate token count based on word count + embedding = embeddings[i].tolist() + + cur.execute( + "SELECT COUNT(*) FROM embeddings WHERE content = %s", + (content,), + ) + count = cur.fetchone()[0] + if count == 0: + cur.execute( + "INSERT INTO embeddings (content, tokens, embedding) VALUES (%s, %s, %s)", + (content, tokens, embedding), + ) + conn.commit() + + cur.execute("SELECT COUNT(*) as cnt FROM embeddings;") + num_records = cur.fetchone()[0] + logger.info(f"Number of vector records in table: {num_records}") + + # calculate the index parameters according to best practices + num_lists = max(num_records / 1000, 10) + if num_records > 1000000: + num_lists = math.sqrt(num_records) + + # use the cosine distance measure, which is what we'll later use for querying + cur.execute( + f"CREATE INDEX IF NOT EXISTS embeddings_idx ON embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = {num_lists});" + ) + conn.commit() - cur.close() - conn.close() + except Exception as e: + logger.error(f"Error in index_generator: {e}") + raise + finally: + if conn: + conn.close() From 9f7cd9e859ab26924880cc7c2133e507a6efcd7e Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 08:36:02 +0100 Subject: [PATCH 14/32] add code inspo acknowledgements --- llm-complete-guide/steps/populate_index.py | 4 ++++ llm-complete-guide/utils/llm_utils.py | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index 11e61050..a01dd5f3 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -1,3 +1,7 @@ +# credit to +# https://www.timescale.com/blog/postgresql-as-a-vector-database-create-store-and-query-openai-embeddings-with-pgvector/ +# for providing the base implementation for this indexing functionality + import logging import math from typing import Annotated, List diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 4d52e34c..965cd63a 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -1,3 +1,8 @@ +# credit to langchain for the original base implementation of splitting +# functionality +# https://github.com/langchain-ai/langchain/blob/master/libs/text-splitters/langchain_text_splitters/character.py + + import logging import re from typing import List @@ -17,15 +22,11 @@ logger = logging.getLogger(__name__) -def split_text_with_regex( - text: str, separator: str, keep_separator: bool -) -> List[str]: +def split_text_with_regex(text: str, separator: str, keep_separator: bool) -> List[str]: if separator: if keep_separator: _splits = re.split(f"({separator})", text) - splits = [ - _splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2) - ] + splits = [_splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2)] if len(_splits) % 2 == 0: splits += _splits[-1:] splits = [_splits[0]] + splits From 8964005daa0fd6d0aaef60ff720fa9241da5548f Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 08:45:48 +0100 Subject: [PATCH 15/32] add and update docstrings --- llm-complete-guide/constants.py | 9 +- llm-complete-guide/pipelines/llm_basic_rag.py | 9 +- llm-complete-guide/run.py | 6 +- llm-complete-guide/steps/populate_index.py | 39 +++++++ llm-complete-guide/utils/llm_utils.py | 100 +++++++++++++++--- 5 files changed, 146 insertions(+), 17 deletions(-) diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py index 2d69c6ae..0d619929 100644 --- a/llm-complete-guide/constants.py +++ b/llm-complete-guide/constants.py @@ -10,6 +10,11 @@ RATE_LIMIT = 5 # Maximum number of requests per second # LLM Utils constants -OPENAI_MODEL = "gpt-4-0125-preview" -# OPENAI_MODEL = "gpt-3.5-turbo" +OPENAI_MODEL = "gpt-3.5-turbo" EMBEDDINGS_MODEL = "all-distilroberta-v1" +MODEL_NAME_MAP = { + "gpt4": "gpt-4-0125-preview", + "gpt35": "gpt-3.5-turbo", + "claude3": "claude-3-opus-20240229", + "claudehaiku": "claude-3-haiku-20240307", +} diff --git a/llm-complete-guide/pipelines/llm_basic_rag.py b/llm-complete-guide/pipelines/llm_basic_rag.py index 7dbe88c4..0a4e5fdd 100644 --- a/llm-complete-guide/pipelines/llm_basic_rag.py +++ b/llm-complete-guide/pipelines/llm_basic_rag.py @@ -10,8 +10,15 @@ @pipeline def llm_basic_rag() -> None: - """Pipeline to train a basic RAG model.""" + """Executes the pipeline to train a basic RAG model. + This function performs the following steps: + 1. Scrapes URLs using the url_scraper function. + 2. Loads documents from the scraped URLs using the web_url_loader function. + 3. Preprocesses the loaded documents using the preprocess_documents function. + 4. Generates embeddings for the preprocessed documents using the generate_embeddings function. + 5. Generates an index for the embeddings and documents using the index_generator function. + """ urls = url_scraper() docs = web_url_loader(urls=urls) processed_docs = preprocess_documents(documents=docs) diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py index 2a776214..5b5a7e61 100644 --- a/llm-complete-guide/run.py +++ b/llm-complete-guide/run.py @@ -79,7 +79,11 @@ def main( """Main entry point for the pipeline execution. Args: - no_cache: If `True` cache will be disabled. + basic_rag (bool): If `True`, the basic RAG pipeline will be run. + rag_query (Optional[str]): If provided, the RAG model will be queried with this string. + model (str): The model to use for the completion. Default is OPENAI_MODEL. + no_cache (bool): If `True`, cache will be disabled. + """ pipeline_args = {"enable_cache": not no_cache} diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index a01dd5f3..b9044fb4 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -26,6 +26,18 @@ def preprocess_documents( documents: List[str], ) -> Annotated[List[str], ArtifactConfig(name="split_chunks")]: + """ + Preprocesses a list of documents by splitting them into chunks. + + Args: + documents (List[str]): A list of documents to be preprocessed. + + Returns: + Annotated[List[str], ArtifactConfig(name="split_chunks")]: A list of preprocessed documents annotated with an ArtifactConfig. + + Raises: + Exception: If an error occurs during preprocessing. + """ try: log_artifact_metadata( artifact_name="split_chunks", @@ -46,6 +58,18 @@ def preprocess_documents( def generate_embeddings( split_documents: List[str], ) -> Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: + """ + Generates embeddings for a list of split documents using a SentenceTransformer model. + + Args: + split_documents (List[str]): A list of documents that have been split into chunks. + + Returns: + Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: The generated embeddings for each document chunk, annotated with an ArtifactConfig. + + Raises: + Exception: If an error occurs during the generation of embeddings. + """ try: model = SentenceTransformer(EMBEDDINGS_MODEL) @@ -67,6 +91,21 @@ def index_generator( embeddings: np.ndarray, documents: List[str], ) -> None: + """ + Generates an index for the given embeddings and documents. + + This function creates a database connection, installs the pgvector extension if not already installed, + creates an embeddings table if it doesn't exist, and inserts the embeddings and documents into the table. + It then calculates the index parameters according to best practices and creates an index on the embeddings + using the cosine distance measure. + + Args: + embeddings (np.ndarray): The embeddings to index. + documents (List[str]): The documents corresponding to the embeddings. + + Raises: + Exception: If an error occurs during the index generation. + """ try: conn = get_db_conn() with conn.cursor() as cur: diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 965cd63a..2e9a6e58 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -10,7 +10,7 @@ import litellm import numpy as np import psycopg2 -from constants import EMBEDDINGS_MODEL, OPENAI_MODEL +from constants import EMBEDDINGS_MODEL, MODEL_NAME_MAP, OPENAI_MODEL from pgvector.psycopg2 import register_vector from psycopg2.extensions import connection from sentence_transformers import SentenceTransformer @@ -22,11 +22,28 @@ logger = logging.getLogger(__name__) -def split_text_with_regex(text: str, separator: str, keep_separator: bool) -> List[str]: +def split_text_with_regex( + text: str, separator: str, keep_separator: bool +) -> List[str]: + """Splits a given text using a specified separator. + + This function splits the input text using the provided separator. The separator can be included or excluded + from the resulting splits based on the value of keep_separator. + + Args: + text (str): The text to be split. + separator (str): The separator to use for splitting the text. + keep_separator (bool): If True, the separator is kept in the resulting splits. If False, the separator is removed. + + Returns: + List[str]: A list of strings resulting from splitting the input text. + """ if separator: if keep_separator: _splits = re.split(f"({separator})", text) - splits = [_splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2)] + splits = [ + _splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2) + ] if len(_splits) % 2 == 0: splits += _splits[-1:] splits = [_splits[0]] + splits @@ -45,6 +62,22 @@ def split_text( keep_separator: bool = False, strip_whitespace: bool = True, ) -> List[str]: + """Splits a given text into chunks of specified size with optional overlap. + + Args: + text (str): The text to be split. + separator (str, optional): The separator to use for splitting the text. Defaults to "\n\n". + chunk_size (int, optional): The maximum size of each chunk. Defaults to 4000. + chunk_overlap (int, optional): The size of the overlap between consecutive chunks. Defaults to 200. + keep_separator (bool, optional): If True, the separator is kept in the resulting splits. If False, the separator is removed. Defaults to False. + strip_whitespace (bool, optional): If True, leading and trailing whitespace is removed from each split. Defaults to True. + + Raises: + ValueError: If chunk_overlap is larger than chunk_size. + + Returns: + List[str]: A list of strings resulting from splitting the input text into chunks. + """ if chunk_overlap > chunk_size: raise ValueError( f"Got a larger chunk overlap ({chunk_overlap}) than chunk size " @@ -91,6 +124,19 @@ def split_documents( keep_separator: bool = False, strip_whitespace: bool = True, ) -> List[str]: + """Splits a list of documents into chunks. + + Args: + documents (List[str]): The list of documents to be split. + separator (str, optional): The separator to use for splitting the documents. Defaults to "\n\n". + chunk_size (int, optional): The maximum size of each chunk. Defaults to 4000. + chunk_overlap (int, optional): The size of the overlap between consecutive chunks. Defaults to 200. + keep_separator (bool, optional): If True, the separator is kept in the resulting splits. If False, the separator is removed. Defaults to False. + strip_whitespace (bool, optional): If True, leading and trailing whitespace is removed from each split. Defaults to True. + + Returns: + List[str]: A list of chunked documents. + """ chunked_documents = [] for doc in documents: chunked_documents.extend( @@ -107,6 +153,14 @@ def split_documents( def get_db_conn() -> connection: + """Establishes and returns a connection to the PostgreSQL database. + + This function retrieves the password for the PostgreSQL database from a secret store, + then uses it along with other connection details to establish a connection. + + Returns: + connection: A psycopg2 connection object to the PostgreSQL database. + """ pg_password = Client().get_secret("postgres_db").secret_values["password"] CONNECTION_DETAILS = { @@ -121,10 +175,19 @@ def get_db_conn() -> connection: def get_topn_similar_docs(query_embedding, conn, n: int = 5): + """Fetches the top n most similar documents to the given query embedding from the database. + + Args: + query_embedding (list): The query embedding to compare against. + conn (psycopg2.extensions.connection): The database connection object. + n (int, optional): The number of similar documents to fetch. Defaults to 5. + + Returns: + list: A list of tuples containing the content of the top n most similar documents. + """ embedding_array = np.array(query_embedding) register_vector(conn) cur = conn.cursor() - # Get the top n most similar documents using the KNN <=> operator cur.execute( f"SELECT content FROM embeddings ORDER BY embedding <=> %s LIMIT {n}", (embedding_array,), @@ -135,14 +198,18 @@ def get_topn_similar_docs(query_embedding, conn, n: int = 5): def get_completion_from_messages( messages, model=OPENAI_MODEL, temperature=0.4, max_tokens=1000 ): - if model == "gpt4": - model = "gpt-4-0125-preview" - elif model == "gpt35": - model = "gpt-3.5-turbo" - elif model == "claude3": - model = "claude-3-opus-20240229" - elif model == "claudehaiku": - model = "claude-3-haiku-20240307" + """Generates a completion response from the given messages using the specified model. + + Args: + messages (list): The list of messages to generate a completion from. + model (str, optional): The model to use for generating the completion. Defaults to OPENAI_MODEL. + temperature (float, optional): The temperature to use for the completion. Defaults to 0.4. + max_tokens (int, optional): The maximum number of tokens to generate. Defaults to 1000. + + Returns: + str: The content of the completion response. + """ + model = MODEL_NAME_MAP.get(model, model) completion_response = litellm.completion( model=model, messages=messages, @@ -152,8 +219,15 @@ def get_completion_from_messages( return completion_response.choices[0].message.content -# Helper function: get embeddings for a text def get_embeddings(text): + """Generates embeddings for the given text using a SentenceTransformer model. + + Args: + text (str): The text to generate embeddings for. + + Returns: + np.ndarray: The generated embeddings. + """ model = SentenceTransformer(EMBEDDINGS_MODEL) return model.encode(text) From 7ec4393dc51289ef3cb20903e0c69091b7eb79e5 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 15:31:53 +0100 Subject: [PATCH 16/32] remove unused code and use zenml urls --- llm-complete-guide/steps/web_url_loader.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/llm-complete-guide/steps/web_url_loader.py b/llm-complete-guide/steps/web_url_loader.py index 036cb73a..e953f4b8 100644 --- a/llm-complete-guide/steps/web_url_loader.py +++ b/llm-complete-guide/steps/web_url_loader.py @@ -14,8 +14,6 @@ from typing import List -# from langchain.docstore.document import Document -# from langchain_community.document_loaders import UnstructuredURLLoader from unstructured.partition.html import partition_html from zenml import step @@ -32,7 +30,7 @@ def web_url_loader(urls: List[str]) -> List[str]: """ document_texts = [] for url in urls: - elements = partition_html(url="https://python.org/") + elements = partition_html(url=url) text = "\n\n".join([str(el) for el in elements]) document_texts.append(text) return document_texts From aad2972bb9aba6c96ce801d76f34318168cf4cbb Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 15:58:40 +0100 Subject: [PATCH 17/32] use smaller embedding model --- llm-complete-guide/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py index 0d619929..6be722e7 100644 --- a/llm-complete-guide/constants.py +++ b/llm-complete-guide/constants.py @@ -11,7 +11,7 @@ # LLM Utils constants OPENAI_MODEL = "gpt-3.5-turbo" -EMBEDDINGS_MODEL = "all-distilroberta-v1" +EMBEDDINGS_MODEL = "sentence-transformers/all-MiniLM-L12-v2" MODEL_NAME_MAP = { "gpt4": "gpt-4-0125-preview", "gpt35": "gpt-3.5-turbo", From cc320fc6d2c84266b93eabbc481192a9604fe5b8 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 16:10:52 +0100 Subject: [PATCH 18/32] update the dimensionality to match the new embedding model --- llm-complete-guide/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py index 6be722e7..5355c200 100644 --- a/llm-complete-guide/constants.py +++ b/llm-complete-guide/constants.py @@ -3,7 +3,7 @@ CHUNK_SIZE = 256 CHUNK_OVERLAP = 50 EMBEDDING_DIMENSIONALITY = ( - 768 # Update this to match the dimensionality of the new model + 384 # Update this to match the dimensionality of the new model ) # Scraping constants From 830de9f4aa19cffefba14ee6a1d86bca6beca28e Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 16:12:35 +0100 Subject: [PATCH 19/32] no cache for embeddings generation --- llm-complete-guide/steps/populate_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index b9044fb4..ba967ca2 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -54,7 +54,7 @@ def preprocess_documents( raise -@step +@step(enable_cache=False) def generate_embeddings( split_documents: List[str], ) -> Annotated[np.ndarray, ArtifactConfig(name="embeddings")]: From 0c7cd69d49bb03b9634f7b6b84dad14a305a8af3 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 16:16:07 +0100 Subject: [PATCH 20/32] fix constant --- llm-complete-guide/constants.py | 1 - 1 file changed, 1 deletion(-) diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py index 5355c200..b0103238 100644 --- a/llm-complete-guide/constants.py +++ b/llm-complete-guide/constants.py @@ -1,5 +1,4 @@ # Vector Store constants -EMBEDDINGS_MODEL = "all-distilroberta-v1" CHUNK_SIZE = 256 CHUNK_OVERLAP = 50 EMBEDDING_DIMENSIONALITY = ( From fb8a2ea3fa46e6e4ff240486121ecd28804ff512 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Wed, 27 Mar 2024 17:55:12 +0100 Subject: [PATCH 21/32] visualise embeddings --- llm-complete-guide/output.png | Bin 0 -> 59926 bytes llm-complete-guide/output2.png | Bin 0 -> 82815 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 llm-complete-guide/output.png create mode 100644 llm-complete-guide/output2.png diff --git a/llm-complete-guide/output.png b/llm-complete-guide/output.png new file mode 100644 index 0000000000000000000000000000000000000000..adbc43d1a1fc892490d7d1381c01d9c8d761b625 GIT binary patch literal 59926 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 Inl{1z1^Y;0`2YX_ literal 0 HcmV?d00001 diff --git a/llm-complete-guide/output2.png b/llm-complete-guide/output2.png new file mode 100644 index 0000000000000000000000000000000000000000..ed6df64a2b90b9cff5ad3371d2669b53edb08ff0 GIT binary patch literal 82815 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 z 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%0DQTdnUxt8xIZ1cNPz#!X>);945l5PPrpL-|Tl4 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�tkDx7Au4>(KCU8;h)eTFp{%4YoJunmY>%3v;XM z7_JT{BLp39uhZ~aA2z$f?W5|*jN_~EY9QA z$*R|{Pfhr5sISblCyoW+##={zdllE$Pgcak)5*vXwIS0tG3jQJ@`$Y28Z`Q_8i=d$ zt>qqviDE)G-0v=Y`oF91f)0utGoWb}W3g$`adC6X@vPl%O=?nV0uQQN0}pqH>fOA@ zT#3(8Q8n7w*tlNyTK@Vsma=(!t@-o^=$9}=yoi4E=%<^T8`dr}AccvZer`2Qma-sm<5at)gOAUY%cV%?qNL^sBz<~fvM54v9r)C!I)B1Kjhq* sQWxgdg`~~@%nwPl62AQR&Ki&8lKi4JlP0g6z=N+FN;j_-E1HM=4}7) Date: Wed, 27 Mar 2024 18:03:34 +0100 Subject: [PATCH 22/32] tiny tweaks to params --- llm-complete-guide/constants.py | 2 +- llm-complete-guide/steps/populate_index.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/llm-complete-guide/constants.py b/llm-complete-guide/constants.py index b0103238..bcc81902 100644 --- a/llm-complete-guide/constants.py +++ b/llm-complete-guide/constants.py @@ -1,5 +1,5 @@ # Vector Store constants -CHUNK_SIZE = 256 +CHUNK_SIZE = 500 CHUNK_OVERLAP = 50 EMBEDDING_DIMENSIONALITY = ( 384 # Update this to match the dimensionality of the new model diff --git a/llm-complete-guide/steps/populate_index.py b/llm-complete-guide/steps/populate_index.py index ba967ca2..ad664e91 100644 --- a/llm-complete-guide/steps/populate_index.py +++ b/llm-complete-guide/steps/populate_index.py @@ -22,7 +22,7 @@ logger = logging.getLogger(__name__) -@step +@step(enable_cache=False) def preprocess_documents( documents: List[str], ) -> Annotated[List[str], ArtifactConfig(name="split_chunks")]: From 4ed2552ddea361bb4b9f9163c13fd5ab68da9c70 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Tue, 2 Apr 2024 16:01:36 +0200 Subject: [PATCH 23/32] add images --- .../{output2.png => .assets/tsne.png} | Bin llm-complete-guide/{output.png => .assets/umap.png} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename llm-complete-guide/{output2.png => .assets/tsne.png} (100%) rename llm-complete-guide/{output.png => .assets/umap.png} (100%) diff --git a/llm-complete-guide/output2.png b/llm-complete-guide/.assets/tsne.png similarity index 100% rename from llm-complete-guide/output2.png rename to llm-complete-guide/.assets/tsne.png diff --git a/llm-complete-guide/output.png b/llm-complete-guide/.assets/umap.png similarity index 100% rename from llm-complete-guide/output.png rename to llm-complete-guide/.assets/umap.png From 66824d8e830dcc2f012a806bdacb224661e3248e Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 08:53:42 +0200 Subject: [PATCH 24/32] update pipeline code to abstract out DB creds --- llm-complete-guide/run.py | 7 ------ llm-complete-guide/utils/llm_utils.py | 34 ++++++++++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/llm-complete-guide/run.py b/llm-complete-guide/run.py index 5b5a7e61..f89da89b 100644 --- a/llm-complete-guide/run.py +++ b/llm-complete-guide/run.py @@ -33,12 +33,6 @@ ZenML LLM Complete Guide project CLI v0.1.0. Run the ZenML LLM RAG complete guide project pipelines. - -Examples: - - \b - # Run the feature feature engineering pipeline - python run.py --feature-pipeline """ ) @click.option( @@ -88,7 +82,6 @@ def main( pipeline_args = {"enable_cache": not no_cache} if rag_query: - # query the llm response = process_input_with_retrieval(rag_query, model=model) print(response) diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 2e9a6e58..5f163ce4 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -4,8 +4,9 @@ import logging +import os import re -from typing import List +from typing import Dict, List import litellm import numpy as np @@ -22,9 +23,7 @@ logger = logging.getLogger(__name__) -def split_text_with_regex( - text: str, separator: str, keep_separator: bool -) -> List[str]: +def split_text_with_regex(text: str, separator: str, keep_separator: bool) -> List[str]: """Splits a given text using a specified separator. This function splits the input text using the provided separator. The separator can be included or excluded @@ -41,9 +40,7 @@ def split_text_with_regex( if separator: if keep_separator: _splits = re.split(f"({separator})", text) - splits = [ - _splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2) - ] + splits = [_splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2)] if len(_splits) % 2 == 0: splits += _splits[-1:] splits = [_splits[0]] + splits @@ -152,6 +149,19 @@ def split_documents( return chunked_documents +def get_local_db_connection_details() -> Dict[str, str]: + """Returns the connection details for the local database. + + Returns: + dict: A dictionary containing the connection details for the local database. + """ + return { + "user": os.getenv("ZENML_SUPABASE_USER"), + "host": os.getenv("ZENML_SUPABASE_HOST"), + "port": os.getenv("ZENML_SUPABASE_PORT"), + } + + def get_db_conn() -> connection: """Establishes and returns a connection to the PostgreSQL database. @@ -161,13 +171,15 @@ def get_db_conn() -> connection: Returns: connection: A psycopg2 connection object to the PostgreSQL database. """ - pg_password = Client().get_secret("postgres_db").secret_values["password"] + pg_password = Client().get_secret("supabase_postgres_db").secret_values["password"] + + local_database_connection = get_local_db_connection_details() CONNECTION_DETAILS = { - "user": "postgres.jjpynzoqhdifcfroyfon", + "user": local_database_connection["user"], "password": pg_password, - "host": "aws-0-eu-central-1.pooler.supabase.com", - "port": "5432", + "host": local_database_connection["host"], + "port": local_database_connection["port"], "dbname": "postgres", } From a70f50b9c802fe23625b15f8cfddcfbf13fc45d5 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 09:01:16 +0200 Subject: [PATCH 25/32] add images --- .../.assets/supabase-connection-string.png | Bin 0 -> 47344 bytes .../.assets/supabase-create-project.png | Bin 0 -> 42853 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 llm-complete-guide/.assets/supabase-connection-string.png create mode 100644 llm-complete-guide/.assets/supabase-create-project.png diff --git a/llm-complete-guide/.assets/supabase-connection-string.png b/llm-complete-guide/.assets/supabase-connection-string.png new file mode 100644 index 0000000000000000000000000000000000000000..d5759d025ae91f8bca7d9a3ac525579cf9b718bc GIT binary patch literal 47344 zcmb@ubyOQ&+dfK53#C|F+G0TpZ7D^9Yp~KH#i2-W3+@`clopCZ@#4jbySqbzyL)g6 z4ncCl^Ss~t`_}rM^WO<;R`$%^_nz7J-ZOLG*L8(JMR`fW#}tpTu&@ZFr9LWSVcm1KVsE3q<6m*Ul^}+E{u=_~7Y*br)yd( z(Da1!o7-1zNq4k$*mkrFmVE}}9vsARDhEv|fD$HxAS;AL4A$P`UHpabs!dk)5XwhP z_IVjj;jco?&EEYX0FpUbMg4m7DgEXq8K1||z=2~|nGXL2Ia#z`$!QR;Ao)8ny6_L5 zzZbEgNgDxTFg!3MJ^^ezyy6o}%gQUch-2twh?(Av4KW@TbcMv`v=Sbeq?;I;Qo?L`u<^&4S zkT#K%!+MV?Kf-!=?->?0rgRVUhjotv>)}6TEUeG>DF0hlzW44wZSG@X1(;(!_)i;6 z%=g_#3bSFX|MPu6?f(C?xEGOz{onGlyQWqM^Sf)nvy%clVqxKv+->);ekYM(Vcmad z{zbz{Lrzw}$kv+Gz}VIh!V0yvyR*UqK?N{HYlxEpE!5h|#!&z&ME6e%0ZjSsFp!S+ zpC(S0LUbB(inQXk4iH)%RyI~PI^oB(w6q`xV-o@8j}ree$LtBwnK?Px2>^kvuCA=E zoUFDEra*RnetsYu2atn<1=E7X(apxm0Lo(HNdIpq|I^P$h@+8%xt)`_tqtv6zXpc3 z&Q3yfbayBE@8{p^gh0*z&zWo-|1&Ji0D*TkKz3F(;QyPMlex+NhuK}tzh?i8>)+FX z?m81tG>1a0z#q-6AvTVfOB3ef;|Kk7n*Xcj|6J(*vefuLmh2q-y#L$ue^vc2)4NL% zP;fAZV7zqa3t@H;@c%mYpY|Z&oumGjWqaYiY5I~ z>TC7x%RL`?_Uwaz3TT75gck5z;0+KGvGZuH< z*^=PBRC?xvz4Aue{vwvPZ$l*Hz7db^fB*iBHd>NjCMwFLZt-$*(sX@WC7`(dwY}x~ zPF?l(n6#?1yy|?O$JIok7O(QmkVV?KXuhu+b|FsoUtfNe@3yU4gcTHK-2YUJi|&Zd z`2V-f=m=&%JFBWn@m~u~!V02HYm6zTfH7fX!R#ArT3F&&{!a%ObDsbI%v&1FV=VsL zc}oa48;C>ubAxPNaj|VzjY*Kcv2o@}{}$8jDTOBPNPf0v&E2>(3H$!wknQ^3K7=05#873>?oR|15~!fs7xfPiw{P7YSn@S3wXXPkH=wCrt`g2EGI{YQ zz<817Id}9tVssIu3~sdelrYMhlWl8TISkLX(=ko5b8y?++K6>?T#xS0aB<_%b*aGy z*Vq}jp|*11>-~wTh>yzCg5sSD**!eXckWJB$F{-~q~EV%Fv^O2J=#fL2RCtdozv;M zNn-0eUhmJRzy5KI3v+h8%}hGuJz%x$RhWSc_6%_1Ld9 zovyJr>P+TZ+B-NX9?ko1yHd~+Dssf}^}GlZGPgo7+L3D4TOE0tuR-idPmGm_O9KF9rLC5Yf+)m%6iP+!e5$04e_sF?53e3iSV-@W>F>u_rTeCKn~ z+YD5{nellK^tv^N?4g`h^0&;pLsU`d&Bj(v1dv(G6FUq*9G#)zMszQJ-E)fvyA|WzgLGZP->Ves4KZkn1U> zAY68|7fphvLM_7o40ekLTzpD3 z%@3IV(PU9QLKFfa88QEAkTFHX$C)g27=`n8Pi`f1`m)rx(qjC59OQI{8`8NQgSf=q z)f0A_SBD!Ul19H+{vt5()RG?J&1_ysn3T`+p5*!r$m}tQM@i`#P8a})Igc!}+rR$0 z#}s_u8`5&{^13h(D&13SVeB}~G7{H)Q&+PNIm5q*4_4g}e;9m!-&a*3TKwe!_!d!` z6I*|vr>2W)BUPuS^JrsB$4&=GT%OI^0(lppM#+o>goLVD^G>63+&2}Y@=O6V-cYxb zVM%zDf-socZ7s6wYQJ7R2U%I{Rrpn8`8x>=n;4`><>&dry^Gv*ENIGg(aTcya8k!r zr*<_=5H@X^s)Omg*`fDMT(k_Zu>xS<@e~nn6vpT}@2pqTM<`TT#dUl1|**FP2k zhTM83@7} zb8zaj*`IDG1x$!2?oZWJ0iGV}_-?pi2g8N2>tikrfW=yu8q=0GK8ibinyBd^-%pTb zDZbe>5ni)%imo4-4>-eVZjk|q@Y~JsJoTH<#fwmf^Lf`eTc4|01U=2Al$^(b$D-DL zSow<&hm)9ky05JV zYhcjl_PFxs_6UeBoAW{6IIovEAYd!iR?}slql_*O&^OI-JH^dSvAkMJr4&7>H`A#x zi&u?{`7sx##XgOmTQYii16$$fi|~A5;hQamxIxc#&hcD;&-Gyp!|05ux?Sqx$b>c@Cm%c);TcZ5Duc7;GcodOZEZg8 zwkm(>vI36w)__lK?5b~){fO@VZ?|S8FPBd<+ zXQt=jJ|(=E+Q;QG=Hg~6u$Y;&3E6N|-<<;X-pRy=;mSkJFK1ZmRH%j{<7mAn^4|oizSocw>5k-DNvEzE&XG&WHws@(55_K{Zx(?$Ne-X+kNY@x zp6{&$16fIRoRpGv>^Po7_iHy|tD8;>5puTfDnHU&svGyL$C33sDi={Amr+OSyv?;q zKDU<$p#wJsw|2?}#j`1+@VcZ%>wq}e!1DmvY+ zZx+#B$&q$8Nv(P}<9abh;X+jd5+QUg@i*~}+V-6}HlwPOw^RHcq( zUC1Z^SJ%y*u8BkJTVmq9peZA?I&N^&*BS506Bmod?5U)}U~cjf8f;9F`(WHv<{oVN zc%?_rtBreqxw8y5Is{5Se52h3h|OY)Ic91;VnTeyCi*+jeoP|Pl>0h9uK60_48yzv z7-B_kMnwu=yj_&-Qn=VwZ`Qm7-JFc>$ikG9f*LncJc4qwA+>Q0Yj0ju-Hc*lqn1Hm zoHmJQIfc08_QCh{Ty_*17szQpw}>oXt_mGS+48Jw%johxpVtS=s3x7ErXt`9s@(*{#e1ToP0 z97i$16VFAmK@B#aE+=4;@Ydt?IIlL}>%jmU7TcMc^n!(oCeQQR)R^{{2dXw2hj!DJ zx}cxLllm^lp&mZU{xA2yHhRa#{BDDOu)Tc4t@p7NVVWf#p!-LC9h^xTV)$18hDU;f z&e}4&>>)d%WewmT!*z_mr+XG}_ZDMan~swa;xs5l?llvl*m7!*-O@ZO#Jk{?hw|-0 z?KUp@mxXvN$(PN)WzFWI&ys&?xS54 z!k9gcr$Gb@7guSGE&pZ1>P1eThHg4$Ak-= zqA5w($nK4+n2!+u}?gha~a;E%7>=&XIp8^lszPe*HnyHd#v} z@&~XW2IdPj%PIqjHe;3xOqwD2i@kC~?rVmQ3=gb>*72bs#l}If;_2=ypmuByvd0TU-ST(Gwr;;_DJi^Onyz9#x&bC8-$$ey`1U z>sR|oc(D_))y#Bx4SrARPkr=*3|QP=KSYS9UTa)-P)$ZxC!zC>#;r0R~Wn3u+#4dK+24T~%ZhjjWMRmb_{&E5KhU9XD$mjh4PoHyUHkmwMfo1Q!m&f<_5`C^M1(=CZ%p z)hKMFG1tsJPNM(8tcMqRE3FOn#|@8;@{AYbcWncMiLOrd(5Eb+X@NP7HkjmNsW#`H z3vH-(wbRyvx>cK+bs>#(O4AjosRq-iDRJa6oo=nSb?s*AZT9t~9bNJKInsi(yzeZC zKh^D3Hwoor+(BnM3cl#3#E<@D8tq&P;z>AeSgs3wJUP{@k;wzq6{)ll0A%Xa#^sB}Pm+LTd|i;{wU#c9NXxp4Ii;Mi zEj?Z(-CR3a6MZW_BIB*5s}k54ohU~(KxfLuAN9G(BuYLw|KDsZ%A6J#j$NAc;-v=% z(mJJ?k%!DGB-Pd7l%gsTTe`b6gYayiO|tmKSliuFKmx{FHemx&UPvk;V_dt!uwxi8 zs5oz)?e{$yP{yDBH!;-tf}PYs7WS>2EP+yli2g{Dr*oEbWmn4T9*ZOOPnN2yeH95J zP}-`L3sTnj{cl^Uo-Y;SZB_r|fBJNUQ?HG4UNGpJTR0}&1CtVGO%||vDS$A697-d> z@!;mLzW8LmwxF*TaRx14L6w~alTNn#k)$t>A8X>4aZY@OtK2;s>XUHtMxkrNBcuWM zH$asjqU=we-8ztgGHA@`;Ql)=-UTgKo|XhcQxg^`o;ByD13sP}jFAhV)_z$K-afhP z>5?@h5TTP>rKx+D+)nXx>rcNop`t%*YO2+{w%BUkv|ZWMWtNNKOBY6eRmNEzmzUfp zncQ%Wtw|E_SvoA+@QN^`YySI_eOkS3&;DC+I!#N}PapaFu=kpm#ILuaCSzeEi! zy;(k_&eUt>R8l#bv(5je;qgs4%m$reh{*YuaZOkIKpF21V%Q&_E6Ob9ump*Qy=t5R zaW;}^$Lg}PJ=?%@`Yxt)j>f2ZnHy`W9#&Nymh!u9cAY35buV9buG`^wEjHvaySEF% z=g@AS$oX%s!^$_qCHsZbDP^?igI7XpdA0Q>yq4NHJm*TY7N^5?kkgVAVmqSBB-V5P zx+d46g=ZeRyps(zf}$61njy=V8x!XXu`0x&h4nkIo|OyXVFj8%Dlikg=hyt_#UoY7 z+Sn+p&gZZWO-sd9{dP&Lj-4VyMhnDmWt+xqtBZM&clkc2aWecCf0=)h!{*`* zGBc1YC(F>G1>QPqS9Kp~N+srg8~gar^AZF9Xkk0YsruZwo1@>L^}xum#o)Bj=l~x*HFUlY zd3_!c3%*e-`(#GwMU$lKU&%*qksB?J;n`LyzkSyj$PyoE=ScP!-#Bk5AiO~oDy^lB z`n`J_Dz&RWbfi-bjm<{22(PuxTaLpXCGrPR+OT4ymla@1#T~6J<+|D^o({o$Z>Sf! z@CGR?&*wZ5vI=a-Wd5mMGl);g)?FU95$_jYuz-1KhAbvc2}Q6QhrG$Cou*!YB!~T2 ztw}>s9xG~++kmUT(&-Y96WG8zH&QOUA~TneQd%}Irn_QIak2LFS_8qMbJ?+|^Y*eO zhct(ffZ$6?Yiq)Z_PONh{Zapa(&c-E-)|@k{QjWNeU{;n)!J>jhTY<$^N4bsk-R$08Qf!^(oYY@VVA=(&uK z$jdLvn~c+bW*b2ej{!+ye&CbF(%fuHBGwaaizb7OHd?QtQ<<2=Ftg}bS9UAuhowa9 zuW>WF@GJoQ3a5Gle6NnDyK&h;y*94EBa~roEiY4vLgX$$A5XU8EO=Se6(& zp)Xd$)oV#R{Mg;>9)SmLFgdSaQAS?NUg|$%BO?4xNI_K3*5IHDMsD~}4XlzHdF&fx z0m>9WTxf@B&m()tC9g184Lwo6Lb!M38e7oT1tJ}Al&!wMuIn`X$!AO_zE!sUYR}fE z9ze0ae-eO{3rBMF(7ffo==Zyzmh#vaz z0vr@)>ZNR_$C58@tn=Zer2KY-I8iQiN!0}bi-}qAI-gfh9uX%N`Z7@fR!OEx!i0?; zwOiSVu)BDCo9qmeZ5BIt=}yZ zSe>$MzIpEh=$ULft9;LNbC(Mux6riL()v~G0qCnjN5M3q|cVIvs$sK4E_#HniPag}?nN$lQQ9&a(;kcAQhi}&I|$y$!VUgIjppiu8gQD#Ho z%_E@QlJ;#`*wG82_?RK{9np#+9!$(2&Gp2~;kEGonjrOY)Ae{6l8`jEDQD%yZa0k% zs_A&VY|FWrk?D5Fhm}(A3RXr7=)3-!N`j53qH*1}?R&!ves0`h#e*zzZOo@o$Ux%* z8%iivRrA%xr@wl3&F*$QTG;g~t+=j~Y#>?W2m{%5Q+rtPul%Hk*>_W^r+6OqS|}0= zUn~VtvcNx>a24G|SA#muAK60NDPgJT%hWg(dH$@neM~m`LZl8kk6BigfQ3Q9g9aEb zwoPx~(*Sg?8Nkf$8PKg)21eyNr{spv@1w(@bEIE1lb)iC*~QuM`7hU^n3yGJw5eA= zp3S>1z9)4zd3{oHzTlxg^Q4NfD6bNe66KLvJi)0{y;1 z#6jiPTYiv!^f56aw;45s%etuAuR~}|ZZ9SFeYi~+RK;7O)mV!Z`|G^0fs&+`^ z`O?|0392m1v2iAjLQUT$J)lQl4*E1@C+f6x(sHgK*=p`!to(bVT0HmZ?W9ad?E+cu z*}n@Z&&ru>Sm%y;={Fd!XV?1Bd8?NhONZziw*K{UMMYOyR3N1Ml16dbdXcf@O1Oe@Gku4w&&N9z~`* zzg9iHdjbi{WADmd^K|DPH3{xBW20}0;SJ5GZsh^K#%LOC)fDd?N_bGXcL~VrmLHj~hAWiFPYNMn0THZww z|4Bp)uS1!A>{)YCz1@>zmc$~~DQ+?gK}pDV^Ox7n^dK4NWlg9MLgxEmhz9E ze}53~PSI_6v(`lO_KU2=Z3)kdTwrYVjJKe-*Xf!lnP(R}q4jZ{R;@lqe`Un^YI*64 z>@DpwkUKe1X=n=dFc2HG+5yCSnP z{q9Xom3a?)mT%Ue*W$JtUE7D=6JbTvw6DIp^n$F~HfI!|Dt%O8!;+vK?$?4pXLY_= zDG(W({(4P#RJER~F3{L!ZRxFbeHW&MWZc7^zLz{pLi{S_hzNHb6u$qazp)1UHI8h(u+ci2S-DFr^#Oz$WkE6D`b$HE zHnbKQu@BHuBjWDn_%%I*n#dW|nc{N6=`;{w=%Avlz?SJgj7s(IZVZcZ zGsKZjVzZk*dK|zdn9;GHylnR&S;^jFu1bB2Kp{2&U(VdcsC9CUVGEMfz;#)NdF3*N zy(JJPqYt=8?#ac`EKMnVy?mS_a%27@GCVc&O>CC|ANhe({V@kJ`>9;K3HizIif0Ex z=X2$sbPr7m*I{SC=6L)=&ay-Kp?s{?%r|0dKwc#1WRczRbFWhu{T0DvEt=s$IZSumIP5{j^ z9i^YeRf(Vf1!+>lIqEqsNwRY*Qxg)G6h-uIh2-F9$(W#bFDdZE^C3P3bj51u8!?Lm z|97coc^GHieX3zbD9%(Z1e**m}7yw4>S%;-ba{|+VP8L5NH#WoGpE2q{<~hjQiaS zMs)=6K4V+hR%{d;W9)aMKC;g=8!q+~XwMM4CzLN!p=0Sd{jx{kxv9@4E)k-F+iB~U z4bmCXfG0*3nZP{rwG53UMl0_8CV71qr}e8y_6`#A&MEFZ(pwY85GPs;%34P}sHnyL zpDyF*gu$2}#crxw^&7oqXY@WwYBvKOpBE3iJ1nIDk3w1{Pu#+?2{}hwYKR_a;smf8 z8#@_NX%DfJIZ#P*%SQ<;eDPd9B_w*ezPTU`kEjIK!ARBNx4$3r~#5K^Nbyj>(V{?W`5 zD@Q$X5|8CYOvO8p9AiYRD}F_Qzs3b*Yyv9}dOCgCKz5w+1i2nt7p*y9{FIhaswO@> z)WgA_>?s%N45)0H2F_n06xh8>_GbVjY4b%VEN&PGfPN$)0G)`snwbitLJHi|weAVo z0et;3py`$+fIc19`N~ddjtX5NKO*w6U=&^AnYajm0ynKd{LiLuI*Df#2~n7YhZ2#s z!*8*na`!ae;!K0h!zlM3;#!<+!#3KM!JCofdfRfgq8?(O#!*n!a_Sm zC#jdHrL?R_QbLZF)!)?oG$=Y1N)s@)QnOO;=`2-yxX9D`g4?n_bmJmx;=*nIA;que zca(b0V&Et4wtC_maCc&-D1itrD?j$Ok*7}j$?uXM8ReF99!XS6?1p@kANaW=B~VQ? z_>NQ7PGG)Ye@DZdwsVP}K_c9D%s%wYSsv#XyOG&Y8?FGs_j6s}4SlCC8IJNm$+D2r zBZB*qD=}{ff`*IEJ=w7ch{c^Jz^1B3S>+k6j{@f%S)F`|C5C0+Et2@ZY#KJ%?PrPIJkr!CPJFs>9trAe^Hk&eGI4f^ghPb4 z6$xV`OwGs9=rNv9!yatqpR z-M_#O4TtMxn|inI30KXxlHg-vw1ygb79XCJ-dbkIN5m~D(lZF3xWAVQ9&Y(gJREhO z{`KZxibfBd`H}D?O$PSg6TZ$o=1UK8%)x!kPNM!=N%VSb$W29W{qhWm8<(@DowN+W zOWEul!)g?CI|7M4 zVT@;%+q{R;GSrEXP2PhTIf0RAF?=-OjUR3{qwNu$jua|^m9S`CC_Xb#VnlkTpJSxu zs`f2*GUMukbDNaR+|rR|-B;X_53$&B@?wcAY<$xlG3?rblH`+iT(gNDb>^_`-$raC z>pwRmgkwmSKI@EqzgX23B>^;ib3$6jzajk2Dv9!{&xpR25z%|u(S9g*aD7^~-H5It zpz(0>dKjo_LDkK?&dkAv$w0evTmt}5yiB~Nx9$s6(-ZA z`gQiQ55j41=nmiEQ=^YET`7Osc)Ar^`R`XW9E6cHkt;l)SCH~Du3hsfC<9B(M{vVU zr%e)MWv&C&-B--lA=Aup_SvRM2ogPD%`=Hi z;T!6QN3mK7s+`2fyT*r3&~l#XMc+~ z>WExp>yjeEhg%EY#piOfZyscY%>Sz^u}s&EJ36RAKgK@XI|u3bVM9 zE+yH-J5;lUXhM`PJaXy<@3ZpYTD=R0t*fm;7^)hrOdb|TbJne9PDzQ9aNXR~m-n&h zttMLAsM^-T2&;BGD3(Bc0Su|{9h`Me;`bT~lfz#X{0eoMXB>qAvcL zW^Q*8{1TYL4w?pBFs;0d@Pn_Xw{if90%l@o2Lw{G@~C1lARARSD%S1}BPTI$85u9x zpnSUCQP92tnJsZNPxJcOdv^SjY=VDdEZ#@}cF5<<&0_slk9j zF9ABpt&m?P3#R0yL-zQDy7v{LaKOA)yA5&zdYgILE1z6m>na@RZRdl%@wG_{!(J1D zxP9`Y{*a_vB29L3`*5^y|G4}kcWWUU7Hvu!sn+kI_8A%QV>yg}FboaG>fq7FCEtF?eov0PpJCWc;`sh?Y3 zcN_5$X7Gh_toDjYInEPo@wJ?N7Qf7lLL~AXQ^<0Pz<&;}EP3{Y340nz|F+b-I`K+P zPfrhee`uU^!4OQMo0Q(GkS+JTn#h=LufpwaYOF{xBx=)Ik0{Fhb;f$|FM{d{0)ixB zr^~$8^uP7^W8T_wU{Cc)V%zBPo^~qhy+lNKa;sRWtzko&K40QDBk@lylTyaH>|g86 zA3EuGy%+b^6XK>nG~gtfn3O$S!!~Uy>n(j`6mzhi@0N-r=RJ0BT+rlfj5Yg51cKot z2{AGd_V~;4382P^InE_P!yDG--0uMANsbINepWr|(;p*uc#pudR4JStc&4z|9?6jK z#&yV%$}5nZ#^9U24}3&=?Q8G691>QSmNpIF5@Bg^M&=3y3ik(D2j6jBY>tF%x*@Oq zHr+dqUK~a=lqYdF`*=@#_3p>~#NGR*R0IC1#48T_$#F(tUhLc2KWLHYn+E*ZZ-zAN zNbPbv-5!s@(E!=Nzw#3Ghx9#r37&TZY_2NE(qObt2jwZtvT?j8n-6Ysd!_{A;0KQmnj z=CuCoWYMa`yjmJy#c3POs};-Nle2oGXJRz?C*_A0c$hMK@@Kt^0?1bo^P&nEuNKzvvvH=-HrcoH&=v2 zX`t~qOcipe0^h+t;)}>QY5ZwAyd!7BAZij&kW6}r|?Hi4DAvt z80saucl(H#;hx?ufy3}GDJrrOIa&-+t-QyxZwj(`W-y}`OvfSnwFjxF%DEs=haqNu zqel-5+SBAl@FXxPtvmU9O6mC}JQ&+#$Qfg03f+##$EDW)HL=KPt?&M}~G|NVcv~ z*~A45Z#9IqVGzt@#I7S=OY)Qb6@I>2VV-{kr$(cNbFv#-$eNe`>Y7wr;Ac)c(!q}m z1VDz4S31z;ABa21-xSCU_CjAR7S|+;x(XX(fOKc_osxE<9VA!!$MPs?p0M3GpUb!% z@RM09+nQy6hd%ahs|4~rsSPpM8K7r(%sDt~yoG1@>oGAviaS$C`B8m2Bq6(-POJ6p zySe1f7H&cfisRCeCp{-`lPu}2;1c({OYOx0yZENB9Ps?ubKXP~KC2L3g zc3l7K_z}2w2j&xXY&p8Mz0{Q;rk>~k-}?FUJ``O2Mnm*qaOCjK+hz#&WcgENhJnnl4EwALX_S43^x*V!e*EWmvLc*RA{th4D zkDz;O_$8{EE05r^>M<2KKXcq5^Z0YquYIfI5>*E2^y(UC+QzNi9~RstD(1A3+2&)v z_|N7yxcCT*fBmK;H9SAc^NA3yjvDHVB5tyKK2Y4r9q`m|+5O;n?N}Rbu|k-(#!zNq zb`;|lqMN-@>A@Me+%NXTI{n$EHVR&2&Axv~@Y>$$NBqnSPZKUPHR1QI7=;DJc$x^O zW#WP=*oafi*(#B@Ut>ZyL5|9{-0%DII-!{9%*lp*)mL&gr4;QN=KUOk+wR$A`|p~x z>Sook?}!QhlOgdP);Aenyf~>^y4X%AS6N7dja1F#3UF9L20xb!;tY(Cc9b0McT2)< z>$_{eU9ulhI}-o~VW(N3Dw9k=RgRJ!;|~47M7IQtG2kSIk{ZA$gZr7^A(gF#T_cqS z$`gRQLhF#364rX87?#4)zI?FYaZm{I+u&lRFAP#I2kO+h_R|bDag8qbHwo&5w#UKs zq4vq?(oCaOrXj*P)~*^hZ>3PPzp9CJSj;R8M#!ZoxONQRH){PX=-YR)GM+xv`29|V z@=sa+PO06Sy|pYndfNg|KF$zT*5kLqx zlK@H_rh?Y?dRk=^^!_qu0~0y=W%E;pqvQDw@v)vKTR^?T=z zzBFJRGKe(QddC?}mq98Q1%b=0<+kd4)%y6ma=i0j^u)X|a@W|5Fo8Vbq$(koX0LPN zU9HK>r^`(acrvLyx0;JSkQpw&w1Y8M^UxK@Y?(-@eWgO^(UPWa7v6^O=r-QIel8xb z&RHLbFFYEolM^N5B};n#5iuk){r20=K;n?E;*)S&)aLxv!sx;8U!00f5gBPG>+#EC zt?l%${q)~u38p>)vm+s$ZRu9G&qy^OEp5|<50J38td}2-;z1eM>MMiS4Ysk;)QBvA zu{3p5cr?Eeaz0&pv{G-yDi)6GFV}=5%4RIT%H*7IUUxnoUN{h-Pz>0fNK{cT(gu5e z$t6GGIvXE!7{wmK#i_&-R3nHf>e*yq1sS7H zO%{_7naqYfACgW^+|1Xycy&*D*UXt`gtD^!wwi7GrA&Qwf9y5pfBHRNlGfd9yQbUl zh1Hyn#uMeWJK41`hHO;zv#p;6-mU;ACaD;n(i6bOB2>Rc;b?6_z8 z#qeZVkqBx|5vI|JVPR7lfgaOv3+y|3QmeR;n1=8bArgV(VMZavMxbA;jEcl53qS_; z!u-8Y^YvIHX}p{oMG(;{Rnj5@RoM&IJ;O5b9>bqVZ-zC#K0$e1uhIaEF?wb=CU&36 z`}i3$v>TN@ObUv*Z^ra z*Jn@z`sSc`AgpvKe(75jNol~sPkFcVqymsrYveKI;)Mi4eSRx`yOL0_8D}i2DrX_l zd0q2&)jWX(x2Nb9u_Sv-O}DdJC6_M=8PVzx4g7UcYhN(v?0x}MjE@8f_Rm-%?bD8a z%0~}5Y$G?Dycov4elLscdGhAq=y0B0UQ7M?35gqj%kcW?fxwgCDa-jvt$P4>)UT$k zd{3unuJ=Q)eOlgQ$EE2>jS^1-z2XRm(F6|D%sl>4Bs;ddy&~vi5q0WQuveHhhKz_K z>}5-C@+U@&JayDP>D*82mzGmqkRDSiS7F$EyQd2aC;5wcoXhJ|zigOI&k-hQ zbd|xCofocOpN-ZL!v_1z(|2xv)Hr*Tyf9i@JK?fQ5dhuVeFgh`TMsUw6JbCer#CxNgQ8yD9XQ!@PqbdcOAE=J6h!ZI9pBFOY` z_c0$2w!&^eQz$wC$6=Z9CMo^W;*V{M>*STJxm@*i4bjI=HjqR`NcJh5*c<7GC@3Gu zgYkG7mypXXsM8|vKvlx?J6fITgI`JCC%gv!aF{(Ae|`xUG$}gmS&P}nWQl2M6>93P z#b1$;7XcW7hb9hDh3b3LZw&O$J(u+$D}E!0)=nw>rC$3P^PHGFwT#+xZ74RA*++_> zN>9o8WH0!ibx}2NUFe}vlW}(fiGQTk&R7(0LD1Lb4$xs*KK{`!p4lX?%imP*e%wrC zjcRpz2?9ae=&$$rC60oZ5wd-T$n52OIL|JMLNRsb*S(-B>*ovB9}{_$6OH358Lgv- zRn!vbSKqJ(wQ)+i^n@MIRL2?RJ#u}TO@^j-=Kvx*E#2mp|sVci+ zc+3~#-G>9BC~ZSPA3XC+r)4%c!o~wE4)j}W9D^wR!)>0%*awOL59Udn9-rCO24vt~O zJOkBtc~zU1qyeho&*+Bme;={dEPc~FFEZt{N*5%|TxB(Q!|I>>%Kc(FAiva@ja%Cv zCM)j8^(!0N+LMf3!c+X}FMu%aDK{OD$B+~m7mfymEh5Pe!A$QY!*0SOE-qdPDz>Tp z{ORu3BgI`$^^$%+y*DTDGt3~D(8q*Ce@Rj%94wD4N&;G)Q;t?dW!JlE49;f39olv=w-Niki_ zL%tD547W*gOEDmUylLMp77b6l3ztycZI;UzVjoyCsD98A@jc{lA(|il&9|D|lpM6? z{Ll=&J4T_XJ`>{`ih7Zm>*2@jhtC;k)op~AQMeU=LkS@A#GwQV--ML$x$xgyr1v9C z4VD!6rRv4fqIV9$_iEV&OOdLzfa-QNJv-bGy`! z&}>!eFx*{9$=Z4B{e6vSjVg!CBrh{PHwSA*Q<`D*Cf7l!S|-2*dj&{7x2nf|zejxzVE@E5`v72z%0Tgg4&gk~yjd z17I~k3qXja)dyen1s)|k7H|Y&hr`|!>&HMYIKNsS#Y`R@3mnQlm<Ik1sdt9kVq zwhzl30|ekQNF1~Im%C{OX-pnnHL#HvQ>x1#T^_PPd)t7Q8n-rbyX!}6i->;lK*>_lwq4W*Wz0EWq zU7zI6%-d(CrWGPpTGNkJ@N6zg46X6-%HJ5A7#cQYfN*RZF<@4oG^aV2bEJcj5tFv$ zXo6(-W|>dZ3%}C=#^(n{g6wYp)&FDieiV^l2gjHW4j|6jahGLI1;nT$QHLr4J&7{Wx z8GU)G`9?U@3mtoZt%?qNLIh7HJrfL7_M>qBsys+(g+$;Kao#L3jIQ2-}N!*_=MB z%|E1WCVm4>_Ob!Z>l~yR#$5!1c4W+pYj!C9_c;Q;wh6w~${B1v9Qe3*@6c|ZnNHMl z=GyK-5q~rVmgJL8W$z6`E{fzn*CkHkCee8Rk-qs|Q9*W!B2NeUj6KO+hHzOEH5F?W zMvoW~r`Eu=&*CMThkm^$W9sX2KTlF{Z! zl`d)+{Au`}6Ti=<^?0+Zp5;Jgq8F-AW8I05iaLy_C|_{@G3Y>Udf70@2kASD+?r>v z+x98o86uo*z$JF&Y>9D7+$wiV_6&P-UX?u`3?K+3q>8O}&sAZQh{5~^2xF$AjDGj# z)5_(#EdlsvgQ;ZVL;MJe)&7A30ZVd7^e3r+XU2J}WT&rMXUWbp610x0=3HYVSo-k@ zvaD~kKC}!Af%(WyYj?T&-W0OfMd^k-Bj5{DanoA0c424o)uWh6F|Dg+{C`;c%7>`B zFI;-01(Yu7?igSI5hbJr>692chwcziI;2CSM3C+->F#EL0cJqDN5DJ2zkC0K`)NMx zJ^SoBd#|<5^E``t9i7#PMoNx6q0#!k0cv7*d3i>u+qr9D1pq z^iyX=IWHJuKVMz1aUUtP0JE|a(9S%N644`C_*7ot@@ph+<}M8TdeQ4!fNsx5L}%;n zeIUZQGRBAPu;I)8FK#1Q%#o@L{E_VP{O+;EZNompfGFisD)91gD_d2{|I-4{Ur%I} zdb&Xx$%jQZS%N9B-X1@nL#}M$~H%Lv&qU-Vl zSZ{l1Xz%~I~U_EsQB|K61V z{{!c!x{{D!-nMVMK9<1o@c8sp_<9yBMSR}eS5@MO!W2B__^qaInor@V+gV&njgD(> zKX-nlQ{cUyb{O$w1@YHmyQM8uMwIo^ee~E4<2y}e3)f$+ zL@f@BSQzhMO9RAY&pv6SmREJQ9uUykt)w^@WuL(fsgP48Iah)sueq&Dy>}^}N-5NC zOnLAYInEe!YtUo9sO4l=qonIvtv_D>r>9~R8^ z*i0enxF+;OqwwBR_gJBw7`{LW;ok2ad@!c2*|THR5X=K+qWmm4ustn``o2yX#RtrN zY{DlXBGN_P7819I>dEfsdYd`{a-<3FMiyNcZrJ^+7`H%Ci87 z+$Kc6Y2!8H_s!S_hvrzTGp_H%r|=(O$Z34lr`ROWQ3Q!{vudHN-xqPyA)y1Mb%(zP zg9Q@I-hl=c&WNA<^tv8+{{45crQ$bVEN>3EqXxJ)iH&FU;y0W4iD(BFRx>^l3_5cz zj#1ub;&{Fu3_^}(H8|MQK}C00+sk%Tml9G3?<$5Nw~iZ;&Xr7r)LgkBOpxDN;a%g@94Dxe)ufXwY|F;pp6PRnQhzNmY(q-D$tfHTxS3}g0 z83@oYv8`rI*pjp@C!KOmBU{6WlJaeExtWQWjk56Bz ze0+}RJ?%^;IM2X(08;|{Dx`#g`gW1(OOGtIRX=|#1XK5625<*?JAt0lZNXK_*HGs; zv_c^NbE2LKB;a)!Y33pW)dXU+Q!9OCW^f^p>`2B{cj~30JGbR`oe?&{yX{ZW8?yzR zI*f8g`FW}l;Y3~tcF!Eo#a*iaT)$SSciYb@G(g7D?b4AWI+!MfleoAkTS`9GSe}b? z^~A10X~j9O&1NJXb(+?)oNpV4l&byB+kfhvPPKc?$2pz#5~Y){9b5e%GV91%e`Ysh2;k2KS zbPub5v(GMM_({|`f!ekehzc9o9R&duIRifg7$v;VBYCy>9gBX^)}81Qhhm*0K_)PS zReQTg9@IBZB2;v#7ImJgePTNu&X5CRV(%N~%-n*`mw6A669T7{`+h~IP%%if?ubCY zy43M|%4w`TF$Gd1ldI^q8Po*>DjQfKTaX0B_V6uZgo6Kb7!6t?KEEVuVSJ^~<;Yz$ zg8!;-nv)qCcYj|0-W6KREhQpS|Es{2?N;jqRwxxPt0tNFNwN~89!-?xYWwGknpEtp zIDV!^C7taUzo;)%GME5M$$t2yYL)LJ76vdR23z15+pXf&#|f#sZO=o=Z1hto zcc^Im;ka9Ftrrd#9sRENu{?1oXv_aoPhXi@;-V*++4i`IBnGo|m4AUST}JA2mwCY- z=~{Fwvg3acQFlvvD;I59mcrqJ$e z;Al6m?45?B+QxJbKjyCtHdWqd!O&!!1cx5a8D%8ZsK#BtnkgJBVO?%{E&*g+8$>GR z#^s`G^K4>SxOrIP;2qWJ47Qs#k?$!q%5-=;y}ck;&Tu;^t-U9yD*}*iX8;}-O9A#+ z12JU@0iI0;#+=Z?r-F~)~pv>H_U z^5$RcVIjD~A+R5=7NCq18hwlA^k>Cmu1G{DsA3_}3mGOzR+nxkZUP5|n$!72vG!kPP3gYX;(AuY%23be`cOrnldF~<{mELxmvLq zp{1ZiyIupThEXQaQ(Anxw=o5@VkVfTsjXz@-%zQz^orlvn}Y8m($j%iHTN~=MJeKT zEt5#+lDPL`7s49LpD=En0pBY8>Yjnq?7x2T1k&^QbZ4_#%L-r$6^bv_=s0}o=CNZT zatXLvxOF_5@zHw^5(sAL)ND2b0o>SLI|bYvC%HdPpK+0?7*Bt3PS8v7N!X^x|1)y# zbM|fdqxXjn(V##6z`9F=by^9I7P*r`F8jQkZzHbb3{LnlIY<rhqP8_o#VHY!Xg117rVhl=3Zf5m#+m-ZZ(#yBHnmU*1`oPXW3w|P%wtrVk&QWRIY zye(jy_94Z0gyr~hkBd3?ooNdDsK~C0;nYZ}4!WtUfr4Q#TYl;$fm*JzMrSvx>?h{4 zy&-W6vZ!K3sftY!_doZFhk15M!r5~#Egl-8zQ@U#qv3#Y@EG&hl_(`DQzw4B`E z3^hKJ{GGpvPi^YrSa~nvvWfK~_oR4}A>Q1r+k+)1V%;b&eQf>;)AoJ)1T4Pv1C{u3 zU{rrk9|i5xBbM48p6J@cym)@L>%rfmc-`f6Gc+4R#)lOP-ikLXA3*{ut_Q%+y0?2B z^)^dk{HLuY47Xde3ZzY-Mc0;z#OC5%Pk@36yAJz=JMPWc8u$XQliA^v{BLbs**;7g z^gJ^=AYM@x(VN>?N#oW22RA?TrD{zqfwjF=s+^MDhu1w%$~Z8>ov7dYW~hYk(i619 zIWQ9r9mib5(*f#A^tIDqgPY~2MZ46(K7=!p^;cd3l8MtsDW$2z^t;;RH3QvE_t#4`2LGgLkE)iKW>-&8lCSR|y^Fz-~v&QZnFuDwqY)EAO}nMQ9tG_j0mS&N1Xzsq6df z7I5==R#hB2J%{QN!!)w2`c_d8c9ZyTWOig{p)QjPJRc2uN&PEX(()!B_CCf22Id~M z5|I({#%ZAUe_rgV!FLe29^x^1))w}yT)4FH(oi|sCVhea3>s((jMyKHA(wW{m#q@U zL~l2P0zsy@T}D9G2}E@Va^#>}c4{)shhbr$)+&h=e*1_#Fa0rwtT0=+v&k&5ulKKu%>!G-NE}kXX^FKHE~4paMhwfC1wHT+Yg$*1BUxVBD8C~)H2V% zn+sso6Y3V*XK=kP{gne)95HYh=gn#F0OJs};C)!4WPY1cnbOT`@-eKBZ0E=M^cmWh zq@+p4KfcjbIs2SG-(1@uvF)c};C4>Sx0B6KyqOnbpN^;8e)QrZ! zwKRtTWGhI}xIR)ETpK-IqF3wAa>=tqt?IRV>b#$XoQ*!0e?OYWTP{>iMjU_UAXeD< zJGt&KK4>~MK7FnWVt1hGEBTE<1n~;+f;saHE6S+~vM)hS2Zu`5fyGu>0wl>KS3=KM zFy#GF-juHCTHhOTrbU(}MgJh(TxU{PYLNkL@hY-p$Oqf@>5^Vf%po*DYVm{7%e$zIdK;^Kc zrlMyWZL3hrl2&>hSIkGecoRol#vdyp@Sqi47iw9>;UJ-k<{;rP*)8P_T)5x&(tR>i z2RSLW-sEM55qd0@zgoDXXR)efX>T*iHZXyV2{;y!yW)`ablg8az44k;wI zOV3M~N$4z%J5Yp9P3taW9SsaT33O=0xNlJJ3;!;Bq#j&?8K_5h&{an_RjdcO_aoH$ zX$eq*Wc(*;6$atTHHa$KSC2S{hV~cqC>)#(R_~Jl4D>i>tYzWl3sGZi#MYixCHXM&t5MzL6h?kh z-_}uX2kpEn^GUU_QfHHDh0aEwa)it_i?R(2Q2{QCXVW3|l;$85GgacsA+&Lt2(^{M z5d;HMnqUgzT)|EV7XQvCcF7OGX-x{B0&UMinV}DQ$pe)<;K3fd07-xh9j!REZXZBKJ((s?)}hU0n~`9^=mj}!51eg z_EvLo#i9{E*I5z!>h~f(v3b3PpOQR}ny(#I5s95PxYjHMv*?1TqM7-qnu2%W&2cYY zn@rYpW~Ne;2R^|q$A&MvZhx4+fNga6PhMh}gG|_?;%_r=O~n-=sY2)B`52eO%uHnPpfaiO-*XGsVJy zJReTI6g7^Tt=y1Tq$zO?jEIV51 zJ<1Cr-B{*wGT!_;Y2Zz76&HLEIX>&sASV_!-lMqiKH-(d6C8@1S=^oD-VwYxWh(Dn zw)Bjrvv8ftvW-#G>ssE(_?h?Z@j#R#M-?7^77RGlBqhpO#H)FCHXX!l7db9!02Ra-1Q?vljlP(t8j^l{bnE>?OUv8_5pg4mgg-W zt|GwdDf9^JAOtLe#?;sKJ3Htzwj`6dt`A^2e+7ww)Hm080D6`Zj(-Ub@Q`thYa)S7+G06obc{qegYCovq#J zFNu&8DPP7a8>G;4Bw16;>gaSr=pVbAWG3=1Cr?0&VSu0g-0H38=k)g0!fLUL%Z`B4 zLuS`Q<}k19YjXbCq!y+usK&fJcYyjn#}`<~EiDab`Q%9rtkNyW1F?BYw~H;QI0t@@Eq19S}RWw@ywV43+Y} zc;q4nFk3%Oiso=q_{~7kxmjX`4A^I;$Kt;REY!KX;wfvGYRAu!_>Hh>p!W2ZQcx;7 ze*&md*eu7od;ewhnW}-1?C4jOV4nzlC$|}gUhXZ zzfW&DovltS^(u`3M*eQ_c$g<-^fF5zPPR*|Qo|464d$$xRc*T7WtopqGZl6z zsS)PC@@LeVy!9t{>DjrJ|A^4O8GkM;Dbnx|PtP_ej`9FO*tTBxs-*#Vj$%+29$hwz zb#LxAni7nJN9Cpd#Xw?j>ep{z;uaT%d51hFK@|d~cRcC017X=uN|fC73<;|aSj zN%cgVEg%xT!x+9)Z>?u)65dBL<9+p$3IDw9Ual4AP!x;n&nQ8Tbeqjm(YHjW`m)GR zs<2lv{!$^mI!pLzeVy9x?_xq51VzKyhs#7OQN>ylmRv+uy;1NEflRy;?8cA2&i_5L z4KIJEfqcM;3x_XP(er8OoNXQvbcm9`awRjC#BxQM1zn4+#O6O&oC17EWR!40g#!&O+%oORDapg-Ve9%L7w*l zToiC{8xLk<@6n0r%n=U?ubyR-m9S0ngxqX$#|7oe|0OePij<`HWyJZ#n-K~eQd7$ zQs1~|5-uetCfW?hOz)Xu??&Wo?_A#X>1pxHO@k%Z!IAz)}0SBej&)>B>N|bgz z>cWxJ(zITsyL=LNQ0aUDJOHze{;%^NB*d7%zp&iMioSD>1ssvj=|(thKE?EIYZM#! zv82KDP_SDQOCIUg?#9QAQ#Jk9R7FY(>uf~Mx|`X6r{PvCY5XYyi_E4r!*Ud@DKpXo z4694sGp|s?_Oe>>F{c4y%6zfT?Uzl-sIZ~;GWJs_p2&UEYbn#0jr+LFg_3WwQAy#= zLveWYF=O$ClO9E!#@+ZwUyJO2seTnXcv!*c`SxDyCi(OopI;TPA%~RidUIcf%S!Y&%3yDh59rFAgK?Pb)<2gu*S^M$~)7}rk9lH9&aax=bjcK3$H)X8EW(= ztBfh+bv1cg{@R+6{y@dWGp1F8S6^F)?`^y<$LFJ0^||VeOaqgbkLSW4s;j;FUrIR^ zO}iOd@10v;fTbt8Go~fTq21hf7d%OQEmpq(D`k_2ncoexeh0&)9H%ymxelHM6LXu_ zFfgbS+>Y-6>UZ+{$DR_NMYEjmh@(&#_?R@>Ou^;KnSvugs3Y@r98&0CN2B0j%TYpH z_yV!p0VJd(JB~ok))>EM$bQ}Hdf$74HLi|Z6~1pgfEU}~8Hi2?dNOmmtLJTj9XH|v zB;*gm8l7f6X+R+Y-K|_A(J7=kCVg?ONQ=V1upj?2SPT=s4>y$tkRFCzAp8DA(w}{b zg01t^7^Dlcv|v}E0G}uU!YkNu)HefH2d0Qa_w4kc3XU z|9%E~uD6zP1`GFScPc2=)Wq0tIF0?@J*6SaBE9RFeHHqHaI2o{la>{y|BRlE5r783 zmCN`%d7+N6&yBM5&X~)hg!$(6y@p>r_48q;nEv>2MLfUXveGl@Fv3YlP3tILg129a|R=Ory>s7?i`G0)-GfUUn~ryB@h!c)fXNU2ov@s%m92fvkFi znCX`p3gS_Ue*dx=7aiz8e(L!5wLj%UA}MHQ2Vstvad=##Ll{C}5H{ zq;bgCHk($V^Os>BRCEv77_{HhW}*-6;9bTv0W;SJs@(!^`tcQZEVaFVsT2nWPL1`E zZ^++p?Y90=JZO@mH=e$GxAnlSFp?Ew@GODmIJ&-XN9D-~ZR7V;OJcl1$_eXA&*~?f z4o<6r)Pv=W6JmsHRSq_&NUo*8*4y^wD(;RUigQUjj&y_p--Z1E&%NNqklDx&6qL4z z)r4-sCPa8Q8*jsn?g>lT%YnG38))`bK>1u3^uQgOO?~dIgYv3MmykddcH4yJr&F-0 zwBzA2N_=C z0dS_9TD2YqKLfWh?htjMMe!y{7RQ&9<+}|~UHzrG*C8McV*&-8UlE)| z_&NNLH-@>z5tTB!21v#d8+9C-ocxG~7p18G2yhL${Z&qMG?lT=)p$0|N_RL#O-j|X z^kEoSztvJ6>X=Or*>CWvU^J1MOH-FRs$a%>+){8ndu){X;-uzggH+KWM<5iXV$aR^oL6= z;qRoE;;zY=CL{CY*RjVyJ@qABn`@Uw%!gWy8yScTUA@r{9X;KUC5`}fjpOBA$Sc5% z+7GcTO%AzM>8}b0;DMg+WXVaGO+1}D-TGX_k<(VMsb0muWY&z_aXolvsmJ+DL^nin zw}iwx#1rES@6ditZvC$)gOtFzSRQamfj}$u$BCjBwk%v9g{NMQVFd`_s|)OWocknX z#iHQ_T!b+t;MnVi5x4)D4prFBX2!@IF9M1Z>YR=P>~4GV)!*>dk*na1Bz`nqJ-sLn z{S8=ur+ATYJ+C}JF=L^mrI4)>#kfFR=`m%9xP|dqH)Ps?sN%9Ii)&&ZGEBhCRw;hntRNIbL$#ta3VsS1c=xZ)Dt(BCj@;M4Mid} zsSuYR1?ldm?77Z(nTu*onU&)XZ7UT)sRKr4q8V?ELMa|JTKRJh>X2>Mvd@>h5BiYt zLAIJ;b34ZDaxNQk^V5hDAzeyf3d*LHG~ZRFJ)Sk|QTrI>90-%9MOIO|+zwZ@)GAwj zahV#%8N*Sl-4_gCq)2^L`XFh;)Th_IV8QBhRF^hyZ5FBa-QudCrs0g@^Namf+r zJ+6^`MlT+#fIR2wR>{O@zm;P?`DHmhRu7{wK2K_AA^rdnS#eaoHJ>^wpk4WZ?s%Tq z4BJw3B`i^vvQwtWx^K|{Rkq1*lk^daiAxq?9`Ma>oKRD1rdvup^&HPYq~Gz>E-M@d zx&I>+?xx2xQ_*#w)W7mzT={WbB`c*g$c!*U5-y)J76k3f1TQc8J&!-0nvP1bxgM-n zGrz}zWG(tuRqcMQ(pyRCVRbPgB8?x(tF`fUg{8^Ryg;ZAhQ_uKWpU zmG+E?2xjQG<7Mm$(HWbvtJYN<5Iw(Q)sV{>&tU#L~J zjtD>7apQsvEkno(cb*i8{7kdmroY)f?YCNTc7uM~cu>Xq0{Vv~1>(e}dZ)t=B;9s9 zz53UsHmaw9pLg2(`P`j2O>OPb9 z5Hrr)l&j!8j7pe>o1bGikXV+^{$a7TgAbn?hhtccPmZ^YS~5kz$v;z1dI)i~q+&qP z7o&#NoTSc&ff0R2skoAmTD5DrYt~t<<5$aEcLL=B)2Wrw-sD`2F9Z@TRNS=gc?(CM z_U&`XC~*j|`qbC-?$DCAQJv(-2;8mcrHj8et`%efg{<=&^g)y!tOPY<_P@?*O|p7J>G6Lko-Bu?eTA@XfqHf%xK1 zGc%d591`66kuld}10MzXCSRrnuh1v{uB3KYsW!uj1Ee&q0TFCXSKRmSa<(ZoX5-ZNFGn*POekHjjxOzfm*?HG6?cyOu!f92(5ZUCN)FcobkQX2 zi4fo69y*s94!`BR-JcE>mp}5L;N0iutGg<)Ef+`UsANSG3b%WOS-^AJX9K=7o?u5l z{(OHQR|y=AZ{-yF!1)Vxub3XhMZ>N^0;VPS_w9R`g@%XscOPwQK2Bv8!CKYsdPW&4 z;S}pxRR-~!8HJ0gX>~(h0?UsOPhGUk;vPooCj?kT1nB93MF+P`Yc7n*U)@rJ|9mZc z+5KWbs}+&S;rYCdGbZa*3D@lJ5Pa)Zm3xfpTR5KYuw;UH#T0 z|6y&LBq89n6rQ(A)vAw?Vf?4U9gwun;g)gJ`pQAVGI6(E=|1M;qSt-SU5<%N=^hET zIBVkmv6vjIci_zEayLcS<*!|-HZSLb#mcV!X?!oy_Yw^lunC1rF`;R7K6NT)sXkV zGv55K=&~8^1QXCIeq}d1N;+VlsCUiY;r9XdTn_K84X)xzK_~E?;W=V|XaBBO;vwD1 z2fE*O7*^d4r4kAqjuUj&JS9krg3SA0_wwGS%k0hkVwb%dzNSsTUNdGnMeMxTjZxpn z;HG8i8K;pxr|hGU`TOyDAO=Mi;f_(1wZFSxJ0I`ovpZ5YthEbu{nT+2F|NiOGWN!P ziV%wkH`FNsk63x!BxZNYA#u#-a5o2QYRavW)+tO8IrjeBIT?tBNGEV2lC)yZP9-tM z^kBFlzpa07#7t4oleun0(B{uDf2aUg1v6GqJ|0TvEMDNZQKOZB+2!IuDxe}CIx62KVR*p_G}L=|F& zYKM0}VJsb(v;6w6@TIv62_?Ff%Ngz3zrxV&GC6asL~Y9h!Kr_RjMYs~V$o`we2AR? zs|}rFL^W0>ctr_E6$(6EGQv_oTQ&j#n*OWpYX6iH9X6NYM*u+;qL!H#gE{0kZ(jK7 zf3<<4sK(swRi$wx|Lf~DIjX1EV&Aqa;UWvLW%wxNXHvgPdk2jxYGRp8MY_vKEc;M`IvI?Kw?i?af zh?xOMQmjTjmlLf8g>lRnH#7QiOuuVI&2KzDzuuijt{6LyqWm&Yb_#S{_fD zO<`UH5C$Q+R4$bmbxMeXAg>E)JLvC*6!|t#>co69C|%n86NYv8XJSLi@9e^!D4u}$ z|CR$6!vXUN;E!JLm0k=A3GR26oZfDAwmcc+b@q!ts77K@BN#awqk$4cj`^ny9a9x} zEG8@4yw`=<{ zi(%cek2|t_kGrV)!?!4nT@G_-v?bW8NM!`FCUBtelg(yc2_V6bRTh{sGmaMT$wk zg*x&C|Fs=*6gPTu+vV@IOLpPod3x8{$iG=OLPOr5MntX}yG4l7NZv+eLNE$gZ3hcIm7<`Oe;MA+=-*i00WV>{>U}L!( zlPB+ZqNwLQdFQF6^(xvq_i^GI&9|Wpx1oF8|A_TGSTtazD&KJj!>_KorzpC%*NbYP zN;67shFp9dqAkVkio<04FRznrXb7L|jfdC8jk>n3R_O=I`{r=}B^5>DNeEz>QVn)Uug&av zV75vBjSF&G5%9^__T=O5NyDdZjTdQ6?ZtHS9*<$+b!hQ-|2720KY5*%%mEcMgabr)t7E|v<@%d0oIq2GbRFa{r3Ii%Hs9Td0a*PTeLS&|eW2d`1yG9ls=jpbae~;Q;Cd&ix5QBL-6( zYp5YRo2Wa&74PX1gfsKroT63G&B|`E>vAwWlpMb-j3rnZHcK|U-3 zstskc@xk!hp`OhNi3@CJ#Rzr}=wI_iKJIw65rm zZVA3mZ%iWo?`lbj?S6xuxB~+vq*Su|_QuuxJB!%?p>f_8ZtX3vXNjd0ugetM7I7y} zf9`s^ze*3XMHt>V{GD2C?QH+yQd}Nj-!j87xix>YT>ik-mev>iWur_Z|99zTqwlTb zf!^-eQQfEg*6SguCkBU?lunqTJ0aZ@JDUz7SC-=Y+|jq=%yP{5Jn;sm!GFHWK<7I((K5ITLsu;N#yB71bV1#<6|Jwkp> zx}U%l_YJzxO3;8kkbAD)6G%TRaFDaGfq8D%EkvIn?yGii+L| zE!azS>=0IM8v2pjw#@F?TiTq}h;`%`B8mUK3F?b06{LD=-ckX_cclo*o<4OLqd+tX zkLyBS7Q|#fSt8Rt4k+Y)DCcvPA6-ByvNw77XNT0ZVy|{S?qM&(;88&%l?N;kSRJ<1 z+ax(+5b4mF=bxs1k2TgooPV#je`i9?w;tk6F8PjUs;6&KWLCntTS%psrkb}tm>J|J z{j;B3UYlE^ePV7!rHWj6)ieYoASPJ>1x{poq~!r>fE}a}mQZt^`?}Fpu*AYY;e83} znArh0*KhPsVbLJHZ;m~Fl~}>?pNUM>!lFu75WN;Hp1PV_XhS-K5`I6UqmaOWNifvS z=58u~Uj-hHI?SS{;vB7eb|$1w9M3_Mugq#C*d{}#@Oew+#e2}{b84bk;}nAy&n;e0 zc213^oYF_IM3H?Hc7($+$tN3KQ=NUU(b(4Ow=6+cO0?_}0{|D$REivh+$ub9HBp{2pQpS(6{ypiA*S$&=ga70OPu+R1F(gB1+P z$uCj`fYE3kfwGCVi87l--+0Xhp|>9AFL8a3&?CoaDuP7@Q%JHrG2MY3n^dtS=)VG$jBy zy{tcv*l$S#>d4_s1C-eaPDAtElD9g{1%z}J&vIG>zwQ@V93NpEkOG&II>WNhC>io# zeXl}Gbas#w47}SUe#sHqDLSmG-FX?fL^CcWAkQ@n&81tz4+l=nU z%Ry>KfpFd2H-LR!o+y2$EEl>WKLlIRHPgbi;%cj`-$*s}$^ecYUn86dA+eB-S zyWf-e_(W*~7trNX7Hr`F)M~r{e~L`$i;*8NlcI+~3AvYsuiSJ9wJp!=pAqF~lV8?* zRFm=mTWOq|3x);AJ#nq5xakL;FuR+V{uZK+a*Rd3y`^b$+lw8zJ>52zFuQ*GeJOvO z1=$m|Q5~H!pyE2iK!l`BXnNY6V}dA|&>zDG<%)LW@rwv(dnK9&J+blWTg}K>hDWKJ z`Kb8x>tY!HM%I@~AGJ>)VUGBFj<48nyNyY|E~OL3%^FkNq5P5z1@m2}cJj(!!-A5? z`jB1Ph>u4clcPiu^6~K(HA_HF{28V_&H6!KqiR|rc9>Eg<`wm`!1+;Zwh1*^PanfM z@;bCo@gNKwzg?^%pkg3`pDMi8%@r?MaB7m3r3b4D$gMsCl+cCba0dcKFG(e689T)S zBvh^MSz2c?>3!^&Q!39db}X&2zt}_$8o}IIWR4xn0P&hWIDg| zRFJ{^m4>zDyn^`UfP&YpEAlcLrLnO?V>(c3Iun^7Nm(5#**a}tGVp}|c0gg|Kc{5& zNd*Dp5U&tX@AghNFI&u1ueOW!f^(>fZS9kzC_pQZ0+C#=iplLSxnCC0sxCqk*f8tm zK_CAWJmuX&WS*+0dv@ove7+<-#LXNL&eopF>ZEPST=k*Q42A%knL(7PG~8Q%Wft^k zd}9ZcxeUxxr~Un=VU?8PR-na@^}`ZAyx`oGUO?Yv9+e-6%YW1(d_T8C*3zTHQ2XI8 zoGQp?nFXYGhD+GFRjh$LG_r%guqLP>d9*52%M1G1_n-R$LJMs`$Fe53AWv;=WjtDp z;KLWb@|3fex2$=)GRHdqrh@2aoMmE(RjImjU*Q5mQJq%LYJoN`HAu>z;<2=I&+zQK zvVXD|&4aw&pVD};qq8Y;Q$)B6a;|mijWgUYYsLGsnCF+uw9*x371OIJm-!&4Z0-29 zA-v6%rpB`LSBqW~59NU<;M?cQg@p5emJfPpLszK2eonp$?g*^_n7x>%Npm+|$Rx;B zxv#Smex<>n7|fT{Up7)~nik{(M0%Amxev{dce)Sn61JP*3~PBHp>Y$e@v+Rl^SON2 zh2G=C-5Q@KI8)Mt+>NYo+TgS;$Zy)j#Jh9z#u7cOS6{@eokWXGW!~p~Q(G^QX)wtT zlJ(qLJW@UNU$qBH%QruTG05c+2O2dPW&kF$Tzt!oz zVKBva$kxuv1c$R@b9aho>+>mFE3*5)Cuqb0N&_n7RCnvx#JC+rc@tvwr^-}6B`rsN z;K|)wQ*Zb>*0o-P{k8@D)ef@1N| zIVKOib5e15Tg{pq>{{1*?CXBSgX(4Ot=tD+M|#UykWI%=l{>?WG#z0u^y zB@GKz6L6;>mOc~UL*kMS9PlVq!=PjYSlXczxmp;x#P6vR>i9y#Ck}EAfGNt1Q~Uqi z`PnySp-UH(6G3p6C-{&7MPf@DFwMBt{|t75r}}ryt)rZ@)llfrYGyc~5(_;(7ZdV& zJk&jiPsJ^51!$~@pk+eg&(VM;=us)KB5Z6SEH*j3VD%+oqOi{|ySHX~u+2}%(9lF| zm0!#0co6W(4@(43n0&*`n^1L6V}d_g}X@PPzGCpSy;4+Y>rS=srqRmJ{B$S;1NewU0V6%fZbj+4a1)>oc#$Bk7`@i z2{PGQmaDLIKd?~pqb;1VKrW%QHiW5^!54n1Mbz;Ke;36Tcq&&VOI^GB?vh>ODJjjTunwI|?*+H57D`+3kXtQ{UbT`=5MTlH~bd(Mz?5IACo~@{z zQ9Mnu*Hkt-ho?QrY|fb?gmpd3k!7WN7-~l;=RZ2%r@6zwTv7eIp(vlwQecVUBuZdz zVHG>kLf2$G5PR^JbdmY96V}ZhhyPbacjIHjI9ekv?Uq#2q;c!1^!NMk2cDWc4DSD% zi;=c!oc{ui{{`#xqfpVe!zn71bMn7PASo(Xr~68n`hS&Qn$uwz{l$4#^{^iWlVPu} zs^g!3yw=|QKP{bgSX2M||0xM61tlb=QU=|l1pyJp6huH;TDo&I?4!Vt7AXO7B1nfI zjC6E&4jhb8GH}$!@Hf8i-~K+=xz1kaI_JFZ`+h#2&!!`imIV>2V*!FdBn0*#)*<)c zZCSGinw+GGpa-m08>ODcSNETCUX+y+*9s$c7VuSjYo>qeO%V-ClM5osq&Gi(x6@~j zr-b&m_M9ynK+WJx-Gw*#hj}+bAgBQHYfJi#l^D38oDnoUty}3^I3`@AkD3DQ!9Cu2 zVY?N`mHfso)D&9>STG5}5_sEgL@e$?1HVOXKS)-(UmJKFcu``Enmh^ETAYmEf1qaa z(q$tPJR0^i5bI`P!-AR`Yy$C6(|;*2pJV>(kqI2e7fAUi`xnw)sNN;ku@Y4{r}QQN z`3;7kWk}F?puj$Kb@t;dzzO?r%34S2Y5S>tonN$U`4Bq`O7iw6cdwbn z*AYxSIPZA+t3ow!AOy4I!%>YcLEYZ|X}G~BU$W?ZJuFc}vbP~C?7q9y#4&wExBoY> zfE=PA%WhLI#E{i&@d2V!{fBe=#L$|mi}-0eKN>ja&U9OqopvCgACXLTgs<}g5W`}l zjE7D7%PuZgWl*%7H}eeO+}Qpq&-%{}4xom$-TXB}+2b~9^{_Ir=XI?&*NnoW@Vqi{ z$s?>=Art%Z3&=vcSMlN$zgn-Hf*M!Y-aU39ur2#*Au+rh;wT++H}A=TivCHaZ? zCe7}#yttdYb`Lvknw6^GwFeRj_vserm{=h~0WN6|Km$2Dhhs=jLMbnBEr8e0UJ|)Y z{hKoe+PGRM^@j1wb_HL6WP06|+|QN}h8&|qyjvz3^}3vb?Kb6zx4B>wDk6-CO-p$o zWo^WS-iaD{8s4L4C9GU>IF;0&*jOzT&!?Z|8VuCcj$t%9pr0S#Qu_q3q%MDM0rZe8_Siiy{MPi6>YN7^HEzX;>|VqPKobtR+Hg;o)Eli5JNzO_vwSW3b8u<!Aq`L0 zTzCNP{@A6}s>hCnet=12?!ymqUmEQ96gD>|{WVng z*`?v91qH7iqWq6C8mlO+!Zj7+&36lRIeAFT^=v7FOfG3cx_MT{nD*#Lsbrq7BjO{B z*mS8(6&01@8Q}sxiVI?rxwR!fY{p#6q z&YK^=>VVsWf84`|_7`#b@+8^wB!6RVwf(aEsx*nIL*VjLs%OeBl z`vrsE?7Jrj_`madgsFIh*B#}4#VgL^9mC{CsWkaa{<Xjp-;qbfwh=(NYDS zB37?Rxa@R7x8nS8dw1Z8Gfj;ioYg zU1xnZU@EV2%JnD@uoiU6qnK}QO_fo1hX?oQc--9Hd&6&x+%SeQCWMu44oI+3L48EI z*ik*`7Y6=F$vdOsCH3*EQ_p`g<9J`Bbx`_ya1?whQ7`#`Bczp%^o)&kIOuceV|n+; z!zF+l{KVhRl2If*ffxS4H&H^RPjS0{E?LBEa+Yh_Zx@%oS~+x`+szwokZ0l&D`t?{ z(9Oi}y-}ZWbW_E)$0_etxYa)haS^p5E}*od0aZ{NFS_qb^)E-4WVvRrD$$u?EDz6I zqg9`K(#+&?;l^m~?!;1;BBOKKO(c8g`;`;>3(xxl;|8T_)GvZ3uBfWqlsh;` ziEON_2+n9<+pDYxuzq+K_Aw+p>?+)N;wk)*T@p{9xlMgD5iY)nx+z1B@*(0rvM zT11K^RIgcF-Vri^x;^G_XS+nqpH{ch@~Q`_k~Qa4Zw(a)!>SWTm~Z?CK960a;<}*b_^haq}=;uryo^&atuG;B?2c`!2=OWJ>)=X{% zh}#z>wmoP^ze8DLhiNfE7^hL=pGOqkA0J#aFgg1vD(+D44DGzhu6N|`i8ww2x8p(i zDgh>Nv>6LxMuX3Id!w4uKhy0hc`@eqXP+&&pJb*Q{Ufq0Z zJm}!q-eluZzA;v=JkqbocH39aCznh@uW$WyRrePt)LD&&KAn^;InKBCGg7Se?j_;DS4)4 zAFmrLfu}vR+L`w24hM>|ofE%a zEe~+BsnQVyRypISe)eMVi_#wC-^-fwYy>21n~2!j)NT3vG}m__Fnv?ueIGWUFCy49 z=RWicX2AW%Ez+cQNU55~;m}(=h<{;0o%F_{w5lFov+*&Kkbpq3j=r)vD}%e?L5oW& zq&G0Hm)_nh9-l`$nkz4ZZVsz`37r7GzqBpa;E*qU?Wvmh?*Z<%|Vkwsm4A)bSKszKeO6QPPe`3k-ln5{=x8 z^IAT_nNprk1|FZGDyjabtRZdtlx}ssV?ZbJNW(j~Ai`C@4RYIM@QWefye?jxv|;3X zKDN!d&_~aDK3%F-U#1HO89`H&^gP>-O|uc(naRJFpK)X{gXCq$zf0wxNu2hn{@~zF zO=c!XSIre0dzGW#R&EOs2`$=2Rw(|6HFi;)?hc2>eRM@=oaw^B82B3*l_?Qy+RrjS zIm=aMvnIST&eETstr}p7l*R7lw&RmUJ^YOS6b~#bA0LzotjM>_Vf8ro92_ST3?U2s z;C#!gjfB#a(7ggv?1qc+reV=iT3*A)h_z(rb=MLoBE(P=*pkz|9SaEEldR9#F~Vks zEsvKkdOWphp$YT5iAo}86DNUyq4%IK^K0Ye1D}l~@T;T$4w;lD=FOSFY#RGw+UV39 z!d7}@R2QV7cWtb^Srbf5l~(T*feb~6geMqVy!%Z$-2VTcgs(q~t0{J1qNJ&!!Fh7U z)^6A=K=h`8O#P&iW6luh~-QvqEtGbRRZv zNQZ2#&fjP<4OM(Hfuee6DD{dX{X&W>v}I@M6YR?pRT*4cB-J&G2>g%^NSf+ql?W0Xr*k6r7XFxIU-8UapOk3g>^6mX z{)&^ZT7v^bJ=VuJ`1XT!b9t-<)+oi#lc!biS=4BF_CbwawWf3HT8c=7L&&oUv3mZ~ za`~vln(5DEg3a1ov*}Wj;3{#sQnDD&f@jQ$>^X^tnBq zFHX|w2=h*^SEQ)4UCF*HWh95Vj?}^0?a4{5Jk&|SR#mhUS}8U7NRv8rRZPXyul~}= zuZNi_cm-+nX6@1heLxRl#(RmRz9RL-32MNHTnPdfqvbj#uDJR zh1hdd9?{;j4xwr#hdS3W8~wXP7Q`Mi_Qa%yy&sG_{7Ba_F&Hc(X{x zCHK2_&(f@ZZTsD0yiQgFxcaZq(LR)ydO0#+*7l83%*x=+qjameK}|=Qy@CwkY~Ew} z)c!QyYoD6D*f4xxe5@+nXmoYadcG$9S|G6Bd%o{l^7Q>>M$WK2E@U!yWeP*D#s+^ZBI}bT*H_I7HB^vpo6!bRPAK+Q zH{un*WC*sDksre}G;F?;q5A!2t6g*wfLH@?i-?A~3?&0D)&QP<(S>XOOE$Opkh6)8 z-x*dwElI|fA%Z_SiUgX994K!OYq=%zwO*TPeM#pmZRbU*W-t)ie}vZm&U*SAB)>Z> z zCuF93P{}a$jyy`$%lc)T77YB%YZE$gXHPi3PCPwp4JW;)dh>G3CcxO&^Pwn%e7J!^fQnrQO)>D+r`4SO5q zq2%1~BOghmLsuQ})sM6-P`&r(hsw2)yTt`6e}DaURQDU?THXFc3I5xoI8`U~igD^< z-j4;ylIDKgnYfqy+~!y~`#Vkt?&+xsaNly=MaBY{R-@HPY?4ROlt0bRXP!}2#{vSA zNHll+t^q>H4b^dg_3mRY9w5QF@(nyz&d-TExLy^_J?e5kHaYB+&z;yTKF6%sU?*Up-<)Nf zj0)CemvC?=_=6Vr&jY1<85Ked9GCVZi--ueoB@oxk& z(J+vGTLgI2k1*9h4Nk|uSDdP4Id)RK(7mTO0u^Sr>_)iS8gx8*hkEsY_~HKwbg&vT-I*m^0YTNF-q52%&duY;Awu1FjS zXN3A%zVVh%U5#}{+WCI^C2W* zCJBPuWX)&tdSGX|8ewr% zQ>sSnDx;vT-SqtG6?OY!MpX~IaSA8NPM=j4!$=z9`|wHXxNguk3P~ArF)$8_gbd$2 z!UZ@Oa(ck%f%@TgNfP{+^v`^T{%_emnKv)EgLX4RTUTw^bJOpOaJDB&g%&{-&M-`2 zj-5N4$~z)+jE_$R*Yr6%GcS-&ISI;c34k!PHcZ7#$P_AK<$+;FoC4`PyYphx)n+6v zs$)|IE%0pj1ph`E#w0|z_ptV?i0_;4ue=9|G}@Khf{wq@(4C@9wvvG4mS$*L%8Jrw zw{^EBR*L|a{w%b8Y)&jI8d`Fu2{wCzF~u+r;XA3-|LXD>90)=|Z8!5GeV$zpn-~tp zr|f0giMhs#h5qPf__FTcS<_ro?yRqL>t}$Ib^lt*3a?z?dF`5oV!e^h56WaO*fyFG zkzPM;=&kl=O~EJTeFGC-Mhp~ztce|Id5Rn!$f}j~|5>l=KyejB>~@dTfq^801Z1io zE!tmJwm@%{cCjlEpB;F3P+cmaY6MV8)3pJHwx)?n6TU=lrjt%m8^FOzN$LaWh$6Gf= zciqyBbe&bU++m-Zs`yohj2k6N`SXDltjill{uywvo8k$0xrKs_M*t3wQ&25d=U=ng zC`v|bkvEACFB+1nj&XMtYH@ExL;dgs__T0+fh~SI-n#h~i&fOsC;i=~0Dnj&pU0`ILXnsX zkCt0Dp4BEKc=~N&V7-*$ii^kEFST!SJL04;yI>xdJ{LtDH<)g$T^8u=vWu zmn`|d{$Q4r)e3dxujOxUttGCQS)P=(Bi4#qA56<{?26b- zdwt^_k-iK5|KeKsv5W&-Z7}Q9$fm4{7?5?wX!Y4#7^*jCkLNo_+(z9WnQ*F z{L$lFcpsfd+u&B+&1Jv9T6PRmLvh^d7LTm6{-H2A{!fu6H zvct1{fYEU}ymHhn(t|F?X*vE1QH~M zoABJ=C!pWAXKkSk<)kP*E08=Hcfb=3*syf=XD&2g?{JWMY40Zxy_4neD~jl$cnC5Y zSxgD=miv=ucXlvj2J%;YWq>8IboKuzk1Ih_)ZQkGK`W$cp)?PJiBAQsC8L0?$(*b~ z7BzJU4$p|4DDEVP6MiU1X3v(=Yotrgk1;D;eIy;8RCRmbhz~MW9se_=PyOm!HB4Tt z;17ao-+6O*$ll^3#@1oJzjLR_*&e*2oqfqPfB5}LR_a`Yq6N7ivktOE=i(e`@3Gp+ zztBH3drsb9idv_6&fnu~(Qs~U`Bz#2AG=%*ABd*r3rgF|7 zt)Tum>x|mw?Zuy@*jRbL=~G^pZ^Tr@t)FPU*?16v(n7PRp?zl@VFRtF+k$ zvXpPT)A0(|xlO$7ZnMukrfrQ_my+kBxtZAAHhxAfjrQR zpQpARQjP^Va0j2PnV}rV_wr!K>&)beWZv_R1wxcmFP%l8@a(I*+3fLcN4Dc)PrjAqNJupsxTJA_>3uM((ok>i92xXP4_83JXO$4z1&OW)QJA9*OS6 zR!KSSECu6ZV{boA8B(W9#WVyqu6iZuU4xg)qwV$2?HH-+w_iJ-_sM(NvB;37icDBH z)6*IW*Ovupv+|yEMHypkxh84dKT)FuH{uMQq+8}9cH?5my*lC{az9pktC`+(rp31F*hvU zh>owZG3biD@c9uuZlTH)%a^yRFP`J?O}m<M>AoSRyujO8bA{i(xTHkh-i}UXK29cD1X|o)qR_H1rwcfic^Te^ zOgB(DR?g9Pn0AQh(2I#~ejQt8dp%z!5({GikXv)!@|)}p*l8Xfgm+wBjk8rp!rq+R z-)Gl+s;Nat_)%n5_1db?d5l+Z|1k61)rq{kWWV&uWf?lEl!GxLXLH_Cgv)_cVDNj1 zRFG*n76f1bMC<%K6Ix?r55Q=FPCEh2=V?i^AEt^rZ9Fe<4`-}r;q zoWZZC9n4sfl*aa5lY}qSuW|$D>g0CEitn%rhi6ihwl}=?oJ<@!of{2Drl`3`Jo^je z8tG341mYdQTqBp;paiL;JdG|cE%8z{b`4dH@;fCrKH|Fvk=#X@6A2>RzPS-T?z9iI zp?yNe`8U)SzHAF9pO;i2xb0mYRN-{0NUzz|cBtq{pYMmS+EI>!!|*Kp+nHV`&NY1OdV zhJVS=)~xYcti9V$rsiZKJP51T)wVz5kPq=LUCPoyUxQBXxn+-Si)dH96AN>M(Rcyj zy_C8y9sHs&92XAdy2i%-CMi01MRw{Y;LDa3Z(%!BPu9Pvhi*JDHisBV_N#^ZPIuqN zujy^RG{p$ zn>dmQwZ7&jb(Ya;az6IQM7jn;^FE@&KT22F3uS$jh$t$v|LzDOkOtscXz=Pf`6wB5n@f8Zk!U?Uz|FT zf6U&?M*tmaDF}&p`0mNgU#?9-m!#5>G?Db6X*0bM_i@3-1Rn9=d%C)I(;1 za}J(r-85Y^)C}37@MWWgOd!4I((%7PUxdxMsQ?#`Z(#8;&U>{gqfV!jb#d&2vaTIelL8@93Ain3(p zkeen?>Ib@a{(mUCC8KQ?KvVElTB=x->XPq{u7iTY58{*e*Msw#Poo!d-pR0Mw_Z{g zfyX{(o#?(O*c4( z1d{gcQxOqR^Pl}H+VbXo9ViGwsPQPa`M%umHa~4;Mi;XAL~cg7*%iE!>t;gYZTY4P zjvOQU_14&g9+?Gy`15*V=^#%0>_JXjL^(FlqFwL8^X$(T8}*skF)``1$)8!K$tAM^ zH<$AWCWkMltI*A5VARS!^u!t_p^_4+rW{29Ey3N+G!^H4e2BQiGoPf>_+7zv;lx;^ zstm{fx%;CacgfO2Nfv!p#r0Ul-Bn?fUI#tdi-Guhvg^Gh^mO%-V7 z@O3uCuT74{gJLfN^0MOZc>j8`rO)xP#bx44gvq-+>~mK*VUir!Xz}as?QKXKjC$0q zV*^=mFbUzi@RxllYup(;%@qT7?_we3vevvsq^3mjr!kJ&7N{Kc8>2Q|Q5lm`EcFrd zKF00Md#Bwtp5kLNfqiBrVMxHzHzp5PT^r-$27Pm_HJ(@D4K>gcmm<@bz5Lio_~Fxk z^^WbM3JsDuP_g zDPH?Xvq3$J7s6joh&)6;j_MyD6nowA$|1>qQdfMfgjeJ%uf0p%R!WeytSND|sKA<| zK925V#zyd1uKWG;VF$rn1IPQs0fOFwP_4mxEb0L(7Qj~=$xAc7a`p@TaIZ~`tgL&^~fm2SyGVaWpKBZWL**CpKT&Fv&w5~ApiDftcDX==n4xWUm0Z5 z5L(8nRZ4n&h&58*+7^0?OjkH?O3JC~lYQlc_<^fL>BllTMsW|V?HvytT_pUGB>_ViSdc?yC9$00 zhsl18F=a}!yF9!2AcvdQlJqQ++Kd3wK^=E5naXOs1lC3XKEF3_ZsL!b!z2VA!zK8U z808b|2iS(q%Vg8hL0Ri_p;{T%VqG`I*@VKH;^!;ETe{gFv0UfVP};~&VzO7b{p7r* zbkJ1i;E4v!Ez;4p$V6XP}y>@F{FdYm^J{iA4i(;luHePP%i!4s??Qw$m^27FMG<3tV1$pFvAVt zEj%cW9MJCMLnZ<1JG9JmOC(3S1f2v~M(BdMx<3&zqSLhG;eP031^E(f3C0X}mL!=& z5G}iQKE=^^N<=*nWpn<^#@%qQa^r-neq7tTgcGWZ^egzA_cy(4cBA?P$j<(;Gm_X8 z8Rp~E9Go<%xmK-qn3^I>N5RR$uq;8EkZ9;|b&qM(?$*Q7@t_-5`Tw2vHd#J%PQN5; zy^iUTNb9|lAu`fLabC>X_I2wzd|f}ni)^vXp@0ha^DDrxzy)m0>#>S*zP$hY>dbhA zNmz>MMVZkA{e^iVZLNRV0phHZvSGYm6?QH<0q2jprx5~>({2`PEOvyy{qSM?1akO3 zVeWG9}WoYTPv5nt4VW-L~5JLl8nr**0 zN-QMJ7kbd$TJL}r`1<~7{X6n2k({n7+#C8_SKVxJ=;pahPyt~I-uOu`>rCK7M@Mvy z{BinrUd{x>&}zeOgzQ>_FOB3SEC->MaYyeUe(%IFZ8XkmZy{tW+w2Bgd+`I2G&Pp4 zshsRt>oAW+=oR$?GKblC_MNQc**ZV>-~-RMTU~k2HFo_5R3Jx?L*5%C&OS>ceZV!+ zvoQu**;M~`HeAgSt2d~JM*pM_cBYc_bT|rp?p3Su$0x-teX_@-H{>kuYkdElaP3lz z++7e$(`1*R*$j$JdOQ#{_-wqD{ofY6kHoUUAfERa7KY4R_{IL$%$P|gWP$TEc$H77x>9XSVo9X0qSg`N z4I_(x4EJu#^)ud7!D|*7YgI7Vt_yqh1i)+V_s#sdtni}&cQ1Q^!@P0tXE6TxMEgXT z%iLSUq9Eya)C8-SJxDX1go83HE57zKTusM%suUU1u@c8kV8YG7Yq{Lc?jIu7t`ltk z*SP(KPmPVA>g9}B*hIEyqong|o=0)a-(%monC7;0vmE~X zHrWhzGrKSblfRlGYenrXV^cs8ib<{FpFmyt<^I$pI@+0dslMqNfrTOE52@KV^6;5S zVsFJUr6F+304?aO&r`etV5n}W9Y`2uxlPfjlAfUv!1S=`7ei6HCcE~ozDv4hdgIh! zzcd5a+~e#;yuvs^yOb3&Rr0oTuUz2{KJk2gVS2LYaLUuztM-YoAEDu$DnYjiL>YIF zg<3cuZ36s{%i7!sKuRZ5p#R7kXg8#O?I&F7y1S-4Z^pC_7ok_!B{qoAAC@N*q#3J= z?@(xb_R`A+Kt$eal5kX~bXIT{K-x!4JDkhee!5eq)QSDZPwS(&E!3hv@1@NCZ;ZTj zyL8ZnD-!mVhC@rA+%P|QLAz`S6VxiFyBo}~5YLF()twzkfdxNtO}Q}d&VeUmm(*2k z^>zFwrs%GrwUwPmw81K+w&XA`UmirG;0Cr*9PbJjvDQQCR(*?HfQ5Qi{A9SzskxnS zohs#yC~rc+Q>m^Oi=Nw}P9yKG_sfO&#*OjFB!JsU84CT@x)zI~Bh4xb{Y#DcmMz{( z3{I*kb61vseMzd@{Kdb^mh1g0FYwp>S~nCHfGC>x;9ptGhA`gJF(&N3d`U>Xc@9i0 z%kVL5*weK_zEk))_7G-m_3I9ocCRhe)8y&D^>XaY^o|>1tepR}Vn=3L$HrcERAIQ; z1F0zFmfMm9wyl`+>PG*V7-6}cqrs#YKvOeI@t!j$SSG1tz=Yj7^L&6B)Q&pIePgPN zIIlKxnCy0p113hQ`wu@CTJe9)ZWxxXHmW_&{c7FMR%?iOTA(Bw)9Y#M@FB7sW9jmZDL`+0_TmNP zyO#0Rp1z3=c6{7TeRCOxFFA=U(GI&-2f!WsJxRUE?Qqo=ni^`wq6K~M_cpHT(CClP zC%WE%7i^?ZzD;Z+2v84=|M#V)C)J}^IqBF#vXZc6O5|?<>dt<%nIdkmHWHw-KtPZ!nSozk&}8kT~NT){A1u@j=JzM7DqLcnQfq{(aQY!R6L z$1ZpkxoCb<(O@&s4<}T(F{$VJ}?9i@(6h&VFo25eYv523avfa0N99o0LEraET$6DHv5ouf%{ zQN152@_dvo`Aj{2VP&Q>cyVx?3>4hlJb%*Cqy>a5i58e7_M}F8d?smiaIbu;YmNSQ zvBp2LgX4TGzsz+_O#sD56%LNX$wDV9C}PGl{Orw+`pY|}ro-Q*9DjaLa;y*8)+ARg z;FYGT1y)F*DWQIvOteq0%r0gVq)HKo6&qv@z06mlYGSIfuRPo>Y_F+O@%8 zqT{*$s+tb)|H@*R>Sb*m#tx(W?||-0mznNP$dmt^UV8b-RWq!Op_k%6xMc0pWoB(v z?el+5FZL;96s311&Yt2wPX_PHGThr6VDz8CO^q)3WcFTNQc?d0s(342R{M%SSj+x9 rdQO0RvKoaou1h}~$!m&aXE^`^SmNPMNz^|I@=s6O@NuQ4ZOs1zY=20Y literal 0 HcmV?d00001 diff --git a/llm-complete-guide/.assets/supabase-create-project.png b/llm-complete-guide/.assets/supabase-create-project.png new file mode 100644 index 0000000000000000000000000000000000000000..11d39b2b184bfe9ce2851a38d1ecc6917a471174 GIT binary patch literal 42853 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 JE|D_}`ae@w$PWMj literal 0 HcmV?d00001 From 0705761a2215cbce09e4a41206ceb67a7d551c06 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 09:08:04 +0200 Subject: [PATCH 26/32] final README updates --- llm-complete-guide/README.md | 188 ++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 89 deletions(-) diff --git a/llm-complete-guide/README.md b/llm-complete-guide/README.md index 38ae1ce1..575ff921 100644 --- a/llm-complete-guide/README.md +++ b/llm-complete-guide/README.md @@ -1,132 +1,142 @@ -# export your OpenAI key -# SUPABASE credentials? +# 🦜 Production-ready RAG pipelines for chat applications +This project showcases how you can work up from a simple RAG pipeline to a more complex setup that +involves finetuning embeddings, reranking retrieved documents, and even finetuning the +LLM itself. We'll do this all for a use case relevant to ZenML: a question +answering system that can provide answers to common questions about ZenML. This +will help you understand how to apply the concepts covered in this guide to your +own projects. -# ☮️ Fine-tuning open source LLMs using MLOps pipelines +Contained within this project is all the code needed to run the full pipelines. +You can follow along [in our guide](https://docs.zenml.io/user-guide/llmops-guide/) to understand the decisions and tradeoffs +behind the pipeline and step code contained here. You'll build a solid understanding of how to leverage +LLMs in your MLOps workflows using ZenML, enabling you to build powerful, +scalable, and maintainable LLM-powered applications. -Welcome to your newly generated "ZenML LLM Finetuning project" project! This is -a great way to get hands-on with ZenML using production-like template. -The project contains a collection of ZenML steps, pipelines and other artifacts -and useful resources that can serve as a solid starting point for finetuning open-source LLMs using ZenML. - -Using these pipelines, we can run the data-preparation and model finetuning with a single command while using YAML files for [configuration](https://docs.zenml.io/user-guide/production-guide/configure-pipeline) and letting ZenML take care of tracking our metadata and [containerizing our pipelines](https://docs.zenml.io/user-guide/advanced-guide/infrastructure-management/containerize-your-pipeline). - -
-
- - Model version metadata - -
-
+This project contains all the pipeline and step code necessary to follow along +with the guide. You'll need a PostgreSQL database to store the embeddings; full +instructions are provided below for how to set that up. ## :earth_americas: Inspiration and Credit -This project heavily relies on the [Lit-GPT project](https://github.com/Lightning-AI/litgpt) of the amazing people at Lightning AI. We used [this blogpost](https://lightning.ai/pages/community/lora-insights/#toc14) to get started with LoRA and QLoRA and modified the commands they recommend to make them work using ZenML. +The RAG pipeline relies on code from [this Timescale +blog](https://www.timescale.com/blog/postgresql-as-a-vector-database-create-store-and-query-openai-embeddings-with-pgvector/) +that showcased using PostgreSQL as a vector database. We adapted it for our use +case and adapted it to work with Supabase. ## 🏃 How to run -In this project we provide a few predefined configuration files for finetuning models on the [Alpaca](https://huggingface.co/datasets/tatsu-lab/alpaca) dataset. Before we're able to run any pipeline, we need to set up our environment as follows: +This project showcases production-ready pipelines so we use some cloud +infrastructure to manage the assets. You can run the pipelines locally using a +local PostgreSQL database, but we encourage you to use a cloud database for +production use cases. -```bash -# Set up a Python virtual environment, if you haven't already -python3 -m venv .venv -source .venv/bin/activate +### Connecting to ZenML Cloud -# Install requirements -pip install -r requirements.txt -``` +If you run the pipeline using ZenML Cloud you'll have access to the managed +dashboard which will allow you to get started quickly. We offer a free trial so +you can try out the platform without any cost. Visit the [ZenML Cloud +dashboard](https://cloud.zenml.io/) to get started. -### Combined feature engineering and finetuning pipeline +### Setting up Supabase -The easiest way to get started with just a single command is to run the finetuning pipeline with the `finetune-alpaca.yaml` configuration file, which will do both feature engineering and finetuning: +[Supabase](https://supabase.com/) is a cloud provider that provides a PostgreSQL database. It's simple to +use and has a free tier that should be sufficient for this project. Once you've +created a Supabase account and organisation, you'll need to create a new +project. -```shell -python run.py --finetuning-pipeline --config finetune-alpaca.yaml -``` +![](.assets/supabase-create-project.png) -When running the pipeline like this, the trained adapter will be stored in the ZenML artifact store. You can optionally upload the adapter, the merged model or both by specifying the `adapter_output_repo` and `merged_output_repo` parameters in the configuration file. +You'll then want to connect to this database instance by getting the connection +string from the Supabase dashboard. +![](.assets/supabase-connection-string.png) -### Evaluation pipeline +You'll then use these details to populate some environment variables where the pipeline code expects them: -Before running this pipeline, you will need to fill in the `adapter_repo` in the `eval.yaml` configuration file. This should point to a huggingface repository that contains the finetuned adapter you got by running the finetuning pipeline. +```shell +export ZENML_SUPABASE_USER= +export ZENML_SUPABASE_HOST= +export ZENML_SUPABASE_PORT= +``` + +You'll want to save the Supabase database password as a ZenML secret so that it +isn't stored in plaintext. You can do this by running the following command: ```shell -python run.py --eval-pipeline --config eval.yaml +zenml secret create supabase_postgres_db --password="YOUR_PASSWORD" ``` -### Merging pipeline +### Running the RAG pipeline -In case you have trained an adapter using the finetuning pipeline, you can merge it with the base model by filling in the `adapter_repo` and `output_repo` parameters in the `merge.yaml` file, and then running: +To run the pipeline, you can use the `run.py` script. This script will allow you +to run the pipelines in the correct order. You can run the script with the +following command: ```shell -python run.py --merge-pipeline --config merge.yaml +python run.py --basic-rag ``` -### Feature Engineering followed by Finetuning +This will run the basic RAG pipeline, which scrapes the ZenML documentation and stores the embeddings in the Supabase database. + +### Querying your RAG pipeline assets -If you want to finetune your model on a different dataset, you can do so by running the feature engineering pipeline followed by the finetuning pipeline. To define your dataset, take a look at the `scripts/prepare_*` scripts and set the dataset name in the `feature-alpaca.yaml` config file. +Once the pipeline has run successfully, you can query the assets in the Supabase +database using the `--rag-query` flag as well as passing in the model you'd like +to use for the LLM. + +In order to use the default LLM for this query, you'll need an account +and an API key from OpenAI specified as another environment variable: ```shell -python run.py --feature-pipeline --config feature-alpaca.yaml -python run.py --finetuning-pipeline --config finetune-from-dataset.yaml +export OPENAI_API_KEY= ``` -## ☁️ Running with a remote stack +When you're ready to make the query, run the following command: -To finetune an LLM on remote infrastructure, you can either use a remote orchestrator or a remote step operator. Follow these steps to set up a complete remote stack: -- Register the [orchestrator](https://docs.zenml.io/stacks-and-components/component-guide/orchestrators) (or [step operator](https://docs.zenml.io/stacks-and-components/component-guide/step-operators)) and make sure to configure it in a way so that the finetuning step has access to a GPU with at least 24GB of VRAM. Check out our docs for more [details](https://docs.zenml.io/stacks-and-components/component-guide). - - To access GPUs with this amount of VRAM, you might need to increase your GPU quota ([AWS](https://docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html), [GCP](https://console.cloud.google.com/iam-admin/quotas), [Azure](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas?view=azureml-api-2#request-quota-and-limit-increases)). - - The GPU instance that your finetuning will be running on will have CUDA drivers of a specific version installed. If that CUDA version is not compatible with the one provided by the default Docker image of the finetuning pipeline, you will need to modify it in the configuration file. See [here](https://hub.docker.com/r/pytorch/pytorch/tags) for a list of available PyTorch images. - - If you're running out of memory, you can experiment with quantized LoRA (QLoRA) by setting a different value for the `quantize` parameter in the configuration, or reduce the `global_batch_size`/`micro_batch_size`. -- Register a remote [artifact store](https://docs.zenml.io/stacks-and-components/component-guide/artifact-stores) and [container registry](https://docs.zenml.io/stacks-and-components/component-guide/container-registries). -- Register a stack with all these components - ```shell - zenml stack register llm-finetuning-stack -o \ - -a \ - -c \ - [-s ] - ``` - -## 💾 Running with custom data - -To finetune a model with your custom data, you will need to convert it to a CSV file with the columns described -[here](https://github.com/Lightning-AI/litgpt/blob/main/tutorials/prepare_dataset.md#preparing-custom-datasets-from-a-csv-file). - -Next, update the `configs/feature-custom.yaml` file and set the value of the `csv_path` parameter to that CSV file. -With all that in place, you can now run the feature engineering pipeline to convert your CSV into the correct format for training and then run the finetuning pipeline as follows: ```shell -python run.py --feature-pipeline --config feature-custom.yaml -python run.py --finetuning-pipeline --config finetune-from-dataset.yaml +python run.py --rag-query "how do I use a custom materializer inside my own zenml steps? i.e. how do I set it? inside the @step decorator?" --model=gpt4 ``` +Alternative options for LLMs to use include: + +- `gpt4` +- `gpt35` +- `claude3` +- `claudehaiku` + +Note that Claude will require a different API key from Anthropic. See [the +`litellm` docs](https://docs.litellm.ai/docs/providers/anthropic) on how to set this up. + +## ☁️ Running with a remote stack + +The basic RAG pipeline will run using a local stack, but if you want to improve +the speed of the embeddings step you might want to consider using a cloud +orchestrator. Please follow the instructions in [our basic cloud setup guides](https://docs.zenml.io/user-guide/cloud-guide) +(currently available for [AWS](https://docs.zenml.io/user-guide/cloud-guide/aws-guide) and [GCP](https://docs.zenml.io/user-guide/cloud-guide/gcp-guide)) to learn how you can run the pipelines on +a remote stack. + ## 📜 Project Structure The project loosely follows [the recommended ZenML project structure](https://docs.zenml.io/user-guide/starter-guide/follow-best-practices): ``` . -├── configs # pipeline configuration files -│ ├── eval.yaml # configuration for the evaluation pipeline -│ ├── feature-alpaca.yaml # configuration for the feature engineering pipeline -│ ├── feature-custom.yaml # configuration for the feature engineering pipeline -│ ├── finetune-alpaca.yaml # configuration for the finetuning pipeline -│ ├── finetune-from-dataset.yaml # configuration for the finetuning pipeline -│ └── merge.yaml # configuration for the merging pipeline -├── pipelines # `zenml.pipeline` implementations -│ ├── evaluate.py # Evaluation pipeline -│ ├── feature_engineering.py # Feature engineering pipeline -│ ├── finetuning.py # Finetuning pipeline -│ └── merge.py # Merging pipeline -├── steps # logically grouped `zenml.steps` implementations -│ ├── evaluate.py # evaluate model performance -│ ├── feature_engineering.py # preprocess data -│ ├── finetune.py # finetune a model -│ ├── merge.py # merge model and adapter -│ ├── params.py # shared parameters for steps -│ └── utils.py # utility functions -├── .dockerignore -├── README.md # this file -├── requirements.txt # extra Python dependencies -└── run.py # CLI tool to run pipelines on ZenML Stack +├── LICENSE # License file +├── README.md # This file +├── constants.py # Constants for the project +├── pipelines +│   ├── __init__.py +│   └── llm_basic_rag.py # Basic RAG pipeline +├── requirements.txt # Requirements file +├── run.py # Script to run the pipelines +├── steps +│   ├── __init__.py +│   ├── populate_index.py # Step to populate the index +│   ├── url_scraper.py # Step to scrape the URLs +│   ├── url_scraping_utils.py # Utilities for the URL scraper +│   └── web_url_loader.py # Step to load the URLs +└── utils + ├── __init__.py + └── llm_utils.py # Utilities related to the LLM ``` From b6c487d8e4c2ff5e57d69b071d189e02be098e78 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 09:10:08 +0200 Subject: [PATCH 27/32] add RAG pipeline image --- .../.assets/rag-pipeline-zenml-cloud.png | Bin 0 -> 42845 bytes llm-complete-guide/README.md | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 llm-complete-guide/.assets/rag-pipeline-zenml-cloud.png diff --git a/llm-complete-guide/.assets/rag-pipeline-zenml-cloud.png b/llm-complete-guide/.assets/rag-pipeline-zenml-cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..8d008e754ac7a2fdfaa164661364d85d6efc8139 GIT binary patch literal 42845 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

xJzXH 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 Z{|{du=GVm)A{_t#002ovPDHLkV1m|%4pjgE literal 0 HcmV?d00001 diff --git a/llm-complete-guide/README.md b/llm-complete-guide/README.md index 575ff921..9c7e08d4 100644 --- a/llm-complete-guide/README.md +++ b/llm-complete-guide/README.md @@ -7,6 +7,8 @@ answering system that can provide answers to common questions about ZenML. This will help you understand how to apply the concepts covered in this guide to your own projects. +![](.assets/rag-pipeline-zenml-cloud.png) + Contained within this project is all the code needed to run the full pipelines. You can follow along [in our guide](https://docs.zenml.io/user-guide/llmops-guide/) to understand the decisions and tradeoffs behind the pipeline and step code contained here. You'll build a solid understanding of how to leverage @@ -17,7 +19,7 @@ This project contains all the pipeline and step code necessary to follow along with the guide. You'll need a PostgreSQL database to store the embeddings; full instructions are provided below for how to set that up. -## :earth_americas: Inspiration and Credit +## 🙏🏻 Inspiration and Credit The RAG pipeline relies on code from [this Timescale blog](https://www.timescale.com/blog/postgresql-as-a-vector-database-create-store-and-query-openai-embeddings-with-pgvector/) From a11e4f4995e7fad6ffc460c40b0e42e2c7255a4c Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 16:16:12 +0200 Subject: [PATCH 28/32] formatting --- llm-complete-guide/utils/llm_utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/llm-complete-guide/utils/llm_utils.py b/llm-complete-guide/utils/llm_utils.py index 5f163ce4..9a204145 100644 --- a/llm-complete-guide/utils/llm_utils.py +++ b/llm-complete-guide/utils/llm_utils.py @@ -23,7 +23,9 @@ logger = logging.getLogger(__name__) -def split_text_with_regex(text: str, separator: str, keep_separator: bool) -> List[str]: +def split_text_with_regex( + text: str, separator: str, keep_separator: bool +) -> List[str]: """Splits a given text using a specified separator. This function splits the input text using the provided separator. The separator can be included or excluded @@ -40,7 +42,9 @@ def split_text_with_regex(text: str, separator: str, keep_separator: bool) -> Li if separator: if keep_separator: _splits = re.split(f"({separator})", text) - splits = [_splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2)] + splits = [ + _splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2) + ] if len(_splits) % 2 == 0: splits += _splits[-1:] splits = [_splits[0]] + splits @@ -171,7 +175,9 @@ def get_db_conn() -> connection: Returns: connection: A psycopg2 connection object to the PostgreSQL database. """ - pg_password = Client().get_secret("supabase_postgres_db").secret_values["password"] + pg_password = ( + Client().get_secret("supabase_postgres_db").secret_values["password"] + ) local_database_connection = get_local_db_connection_details() From 389779897f8b55706a01833adc804db82980dc6d Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 19:13:22 +0200 Subject: [PATCH 29/32] add super simple RAG pipeline --- llm-complete-guide/most_basic_rag_pipeline.py | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 llm-complete-guide/most_basic_rag_pipeline.py diff --git a/llm-complete-guide/most_basic_rag_pipeline.py b/llm-complete-guide/most_basic_rag_pipeline.py new file mode 100644 index 00000000..18752da0 --- /dev/null +++ b/llm-complete-guide/most_basic_rag_pipeline.py @@ -0,0 +1,159 @@ +import os +import re +import string + +import numpy as np +from openai import OpenAI + + +def preprocess_text(text): + # Lowercase the text + text = text.lower() + # Remove punctuation + text = text.translate(str.maketrans("", "", string.punctuation)) + # Remove extra whitespace + text = re.sub(r"\s+", " ", text).strip() + return text + + +def tokenize(text): + return preprocess_text(text).split() + + +def split_into_chunks(sentence, chunk_size=5): + words = sentence.split() + return [ + " ".join(words[i : i + chunk_size]) + for i in range(0, len(words), chunk_size) + ] + + +def build_vocab(corpus): + vocab = {} + for chunk in corpus: + for word in chunk: + if word not in vocab: + vocab[word] = len(vocab) + return vocab + + +def train_word2vec(corpus, vocab, vector_size, learning_rate, epochs): + embeddings = np.random.uniform(-1, 1, (len(vocab), vector_size)) + for _ in range(epochs): + for chunk in corpus: + for center_word in chunk: + center_word_index = vocab[center_word] + context_words = [word for word in chunk if word != center_word] + for context_word in context_words: + context_word_index = vocab[context_word] + center_word_vec = embeddings[center_word_index] + context_word_vec = embeddings[context_word_index] + error = np.dot(center_word_vec, context_word_vec) + embeddings[center_word_index] -= ( + learning_rate * error * context_word_vec + ) + embeddings[context_word_index] -= ( + learning_rate * error * center_word_vec + ) + return embeddings + + +def cosine_similarity(vec1, vec2): + return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)) + + +def answer_question(query, corpus, vocab, embeddings, top_n=2): + query_tokens = tokenize(query) + query_embeddings = [] + for token in query_tokens: + if token in vocab: + token_embedding = embeddings[vocab[token]] + query_embeddings.append(token_embedding) + if not query_embeddings: + return "I don't have enough information to answer the question." + + query_embedding = np.mean(query_embeddings, axis=0) + similarities = [] + for chunk in corpus: + chunk_tokens = tokenize(" ".join(chunk)) + if chunk_embeddings := [ + embeddings[vocab[token]] + for token in chunk_tokens + if token in vocab + ]: + chunk_embedding = np.mean(chunk_embeddings, axis=0) + similarity = cosine_similarity(query_embedding, chunk_embedding) + similarities.append((chunk, similarity)) + similarities.sort(key=lambda x: x[1], reverse=True) + + if similarities: + top_chunks = [" ".join(chunk[0]) for chunk in similarities[:top_n]] + context = "\n".join(top_chunks) + + client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) + chat_completion = client.chat.completions.create( + messages=[ + { + "role": "system", + "content": f"Based on the provided context, answer the following question: {query}\n\nContext:\n{context}", + }, + { + "role": "user", + "content": query, + }, + ], + model="gpt-3.5-turbo", + ) + + return chat_completion["choices"][0]["message"]["content"].strip() + else: + return "I don't have enough information to answer the question." + + +# Sci-fi themed corpus about "ZenML World" with longer sentences +corpus = [ + "The luminescent forests of ZenML World are inhabited by glowing Zenbots that emit a soft, pulsating light as they roam the enchanted landscape.", + "In the neon skies of ZenML World, Cosmic Butterflies flutter gracefully, their iridescent wings leaving trails of stardust in their wake.", + "Telepathic Treants, ancient sentient trees, communicate through the quantum neural network that spans the entire surface of ZenML World, sharing wisdom and knowledge.", + "Deep within the melodic caverns of ZenML World, Fractal Fungi emit pulsating tones that resonate through the crystalline structures, creating a symphony of otherworldly sounds.", + "Near the ethereal waterfalls of ZenML World, Holographic Hummingbirds hover effortlessly, their translucent wings refracting the prismatic light into mesmerizing patterns.", + "Gravitational Geckos, masters of anti-gravity, traverse the inverted cliffs of ZenML World, defying the laws of physics with their extraordinary abilities.", + "Plasma Phoenixes, majestic creatures of pure energy, soar above the chromatic canyons of ZenML World, their fiery trails painting the sky in a dazzling display of colors.", + "Along the prismatic shores of ZenML World, Crystalline Crabs scuttle and burrow, their transparent exoskeletons refracting the light into a kaleidoscope of hues.", +] + +print("Starting preprocessing stage...") +# Split the corpus into smaller chunks +chunk_size = 45 +corpus_chunks = [] +for sentence in corpus: + chunks = split_into_chunks(preprocess_text(sentence), chunk_size) + corpus_chunks.extend(chunks) + +print("Starting vocabulary building stage...") +# Build vocabulary +vocab = build_vocab(corpus_chunks) + +print("Starting training stage...") +# Train word2vec model +vector_size = 50 +learning_rate = 0.01 +epochs = 200 +embeddings = train_word2vec( + corpus_chunks, vocab, vector_size, learning_rate, epochs +) + +print("Model training complete!") +print("Model evaluation:") +# Ask questions +question1 = "What are Plasma Phoenixes?" +answer1 = answer_question(question1, corpus_chunks, vocab, embeddings) +print(f"Question: {question1}") +print(f"Answer: {answer1}") + +question2 = ( + "What kinds of creatures live on the prismatic shores of ZenML World?" +) +answer2 = answer_question(question2, corpus_chunks, vocab, embeddings) +print(f"Question: {question2}") +print(f"Answer: {answer2}") From 6ba2874281f622e212bdf0c6c9a6da8ced8b61d0 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 19:36:20 +0200 Subject: [PATCH 30/32] even more basic RAG --- llm-complete-guide/most_basic_rag_pipeline.py | 110 +++--------------- 1 file changed, 18 insertions(+), 92 deletions(-) diff --git a/llm-complete-guide/most_basic_rag_pipeline.py b/llm-complete-guide/most_basic_rag_pipeline.py index 18752da0..e5656d25 100644 --- a/llm-complete-guide/most_basic_rag_pipeline.py +++ b/llm-complete-guide/most_basic_rag_pipeline.py @@ -2,7 +2,6 @@ import re import string -import numpy as np from openai import OpenAI @@ -20,75 +19,22 @@ def tokenize(text): return preprocess_text(text).split() -def split_into_chunks(sentence, chunk_size=5): - words = sentence.split() - return [ - " ".join(words[i : i + chunk_size]) - for i in range(0, len(words), chunk_size) - ] - - -def build_vocab(corpus): - vocab = {} - for chunk in corpus: - for word in chunk: - if word not in vocab: - vocab[word] = len(vocab) - return vocab - - -def train_word2vec(corpus, vocab, vector_size, learning_rate, epochs): - embeddings = np.random.uniform(-1, 1, (len(vocab), vector_size)) - for _ in range(epochs): - for chunk in corpus: - for center_word in chunk: - center_word_index = vocab[center_word] - context_words = [word for word in chunk if word != center_word] - for context_word in context_words: - context_word_index = vocab[context_word] - center_word_vec = embeddings[center_word_index] - context_word_vec = embeddings[context_word_index] - error = np.dot(center_word_vec, context_word_vec) - embeddings[center_word_index] -= ( - learning_rate * error * context_word_vec - ) - embeddings[context_word_index] -= ( - learning_rate * error * center_word_vec - ) - return embeddings - - -def cosine_similarity(vec1, vec2): - return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)) - - -def answer_question(query, corpus, vocab, embeddings, top_n=2): - query_tokens = tokenize(query) - query_embeddings = [] - for token in query_tokens: - if token in vocab: - token_embedding = embeddings[vocab[token]] - query_embeddings.append(token_embedding) - if not query_embeddings: - return "I don't have enough information to answer the question." - - query_embedding = np.mean(query_embeddings, axis=0) +def retrieve_relevant_chunks(query, corpus, top_n=2): + query_tokens = set(tokenize(query)) similarities = [] for chunk in corpus: - chunk_tokens = tokenize(" ".join(chunk)) - if chunk_embeddings := [ - embeddings[vocab[token]] - for token in chunk_tokens - if token in vocab - ]: - chunk_embedding = np.mean(chunk_embeddings, axis=0) - similarity = cosine_similarity(query_embedding, chunk_embedding) - similarities.append((chunk, similarity)) + chunk_tokens = set(tokenize(chunk)) + similarity = len(query_tokens.intersection(chunk_tokens)) / len( + query_tokens.union(chunk_tokens) + ) + similarities.append((chunk, similarity)) similarities.sort(key=lambda x: x[1], reverse=True) + return [chunk for chunk, _ in similarities[:top_n]] - if similarities: - top_chunks = [" ".join(chunk[0]) for chunk in similarities[:top_n]] - context = "\n".join(top_chunks) + +def answer_question(query, corpus, top_n=2): + if relevant_chunks := retrieve_relevant_chunks(query, corpus, top_n): + context = "\n".join(relevant_chunks) client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) chat_completion = client.chat.completions.create( @@ -105,12 +51,12 @@ def answer_question(query, corpus, vocab, embeddings, top_n=2): model="gpt-3.5-turbo", ) - return chat_completion["choices"][0]["message"]["content"].strip() + return chat_completion.choices[0].message.content.strip() else: return "I don't have enough information to answer the question." -# Sci-fi themed corpus about "ZenML World" with longer sentences +# Sci-fi themed corpus about "ZenML World" corpus = [ "The luminescent forests of ZenML World are inhabited by glowing Zenbots that emit a soft, pulsating light as they roam the enchanted landscape.", "In the neon skies of ZenML World, Cosmic Butterflies flutter gracefully, their iridescent wings leaving trails of stardust in their wake.", @@ -122,38 +68,18 @@ def answer_question(query, corpus, vocab, embeddings, top_n=2): "Along the prismatic shores of ZenML World, Crystalline Crabs scuttle and burrow, their transparent exoskeletons refracting the light into a kaleidoscope of hues.", ] -print("Starting preprocessing stage...") -# Split the corpus into smaller chunks -chunk_size = 45 -corpus_chunks = [] -for sentence in corpus: - chunks = split_into_chunks(preprocess_text(sentence), chunk_size) - corpus_chunks.extend(chunks) - -print("Starting vocabulary building stage...") -# Build vocabulary -vocab = build_vocab(corpus_chunks) - -print("Starting training stage...") -# Train word2vec model -vector_size = 50 -learning_rate = 0.01 -epochs = 200 -embeddings = train_word2vec( - corpus_chunks, vocab, vector_size, learning_rate, epochs -) +# Preprocess the corpus +corpus = [preprocess_text(sentence) for sentence in corpus] -print("Model training complete!") -print("Model evaluation:") # Ask questions question1 = "What are Plasma Phoenixes?" -answer1 = answer_question(question1, corpus_chunks, vocab, embeddings) +answer1 = answer_question(question1, corpus) print(f"Question: {question1}") print(f"Answer: {answer1}") question2 = ( "What kinds of creatures live on the prismatic shores of ZenML World?" ) -answer2 = answer_question(question2, corpus_chunks, vocab, embeddings) +answer2 = answer_question(question2, corpus) print(f"Question: {question2}") print(f"Answer: {answer2}") From 0662ec58297384c1172a7c26f965b72c5390e136 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 19:40:39 +0200 Subject: [PATCH 31/32] add a third irrelevant question --- llm-complete-guide/most_basic_rag_pipeline.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/llm-complete-guide/most_basic_rag_pipeline.py b/llm-complete-guide/most_basic_rag_pipeline.py index e5656d25..442bcf93 100644 --- a/llm-complete-guide/most_basic_rag_pipeline.py +++ b/llm-complete-guide/most_basic_rag_pipeline.py @@ -83,3 +83,8 @@ def answer_question(query, corpus, top_n=2): answer2 = answer_question(question2, corpus) print(f"Question: {question2}") print(f"Answer: {answer2}") + +irrelevant_question_3 = "What is the capital of Panglossia?" +answer3 = answer_question(irrelevant_question_3, corpus) +print(f"Question: {irrelevant_question_3}") +print(f"Answer: {answer3}") From 6f342a5c7d89e20eff6c565672f5d706de675785 Mon Sep 17 00:00:00 2001 From: Alex Strick van Linschoten Date: Mon, 8 Apr 2024 19:45:08 +0200 Subject: [PATCH 32/32] Refactor preprocess_text and answer_question functions --- llm-complete-guide/most_basic_rag_pipeline.py | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/llm-complete-guide/most_basic_rag_pipeline.py b/llm-complete-guide/most_basic_rag_pipeline.py index 442bcf93..f01f55f8 100644 --- a/llm-complete-guide/most_basic_rag_pipeline.py +++ b/llm-complete-guide/most_basic_rag_pipeline.py @@ -6,11 +6,8 @@ def preprocess_text(text): - # Lowercase the text text = text.lower() - # Remove punctuation text = text.translate(str.maketrans("", "", string.punctuation)) - # Remove extra whitespace text = re.sub(r"\s+", " ", text).strip() return text @@ -33,28 +30,28 @@ def retrieve_relevant_chunks(query, corpus, top_n=2): def answer_question(query, corpus, top_n=2): - if relevant_chunks := retrieve_relevant_chunks(query, corpus, top_n): - context = "\n".join(relevant_chunks) - - client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) - chat_completion = client.chat.completions.create( - messages=[ - { - "role": "system", - "content": f"Based on the provided context, answer the following question: {query}\n\nContext:\n{context}", - }, - { - "role": "user", - "content": query, - }, - ], - model="gpt-3.5-turbo", - ) - - return chat_completion.choices[0].message.content.strip() - else: + relevant_chunks = retrieve_relevant_chunks(query, corpus, top_n) + if not relevant_chunks: return "I don't have enough information to answer the question." + context = "\n".join(relevant_chunks) + client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) + chat_completion = client.chat.completions.create( + messages=[ + { + "role": "system", + "content": f"Based on the provided context, answer the following question: {query}\n\nContext:\n{context}", + }, + { + "role": "user", + "content": query, + }, + ], + model="gpt-3.5-turbo", + ) + + return chat_completion.choices[0].message.content.strip() + # Sci-fi themed corpus about "ZenML World" corpus = [