From 15112d52728363233037cbc812cfceb59d1e4b91 Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Wed, 16 Oct 2024 12:39:52 -0500 Subject: [PATCH 01/11] Updates to pyproject file --- python/pyproject.toml | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 277be4c..edcc881 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,14 +1,44 @@ [project] name = "mall" -version = "0.1.0" -description = "Add your description here" +version = "0.1.9000" +description = "Run multiple 'Large Language Model' predictions against a table. The predictions run row-wise over a specified column." readme = "README.md" -requires-python = ">=3.12" +authors = [ + { name = "Edgar Ruiz", email = "edgar@posit.co" } +] +requires-python = ">=3.9" dependencies = [ "ollama>=0.3.3", "polars>=1.9.0", + "json5>=0.9.25", + "pytest>=8.3.3", + "pytest-cov>=5.0.0", + "pytest-html>=4.1.1", + "pytest-metadata>=3.1.1" +] +classifiers = [ + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Financial and Insurance Industry", + "Intended Audience :: Science/Research", + "Intended Audience :: Healthcare Industry", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Libraries :: Python Modules" ] +keywords = ["llm", "nlp", "polars", "large language models", "natural language processing"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[project.urls] +homepage = "https://github.com/mlverse/mall" +documentation = "https://mlverse.github.io/mall/" +issues = "https://github.com/mlverse/mall/issues" From 30da301a069d612c5b8c9b778a765e0395ab74af Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Wed, 16 Oct 2024 13:42:38 -0500 Subject: [PATCH 02/11] Restores verify section to index --- _freeze/index/execute-results/html.json | 4 +- index.qmd | 60 ++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/_freeze/index/execute-results/html.json b/_freeze/index/execute-results/html.json index c214459..59d528e 100644 --- a/_freeze/index/execute-results/html.json +++ b/_freeze/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "8cd0260bd9b9c35d70e1ee3db107ae7f", + "hash": "991ac920bd4b176aa0812245664ed968", "result": { "engine": "knitr", - "markdown": "---\nformat:\n html:\n toc: true\nexecute:\n eval: true\n freeze: true\n---\n\n\n\n\n\n\n\n\n\n\n\n[![R check](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml)\n[![Python tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml)\n[![R package coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main)\n[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)\n\n\n\nRun multiple LLM predictions against a data frame. The predictions are processed \nrow-wise over a specified column. It works using a pre-determined one-shot prompt,\nalong with the current row's content. `mall` has been implemented for both R\nand Python. The prompt that is use will depend of the type of analysis needed. \n\nCurrently, the included prompts perform the following: \n\n- [Sentiment analysis](#sentiment)\n- [Text summarizing](#summarize)\n- [Classify text](#classify)\n- [Extract one, or several](#extract), specific pieces information from the text\n- [Translate text](#translate)\n- [Custom prompt](#custom-prompt)\n\nThis package is inspired by the SQL AI functions now offered by vendors such as\n[Databricks](https://docs.databricks.com/en/large-language-models/ai-functions.html) \nand Snowflake. `mall` uses [Ollama](https://ollama.com/) to interact with LLMs \ninstalled locally. \n\n\n\nFor **R**, that interaction takes place via the \n[`ollamar`](https://hauselin.github.io/ollama-r/) package. The functions are \ndesigned to easily work with piped commands, such as `dplyr`. \n\n```r\nreviews |>\n llm_sentiment(review)\n```\n\n\n\nFor **Python**, `mall` is a library extension to [Polars](https://pola.rs/). To\ninteract with Ollama, it uses the official\n[Python library](https://github.com/ollama/ollama-python).\n\n```python\nreviews.llm.sentiment(\"review\")\n```\n\n## Motivation\n\nWe want to new find ways to help data scientists use LLMs in their daily work. \nUnlike the familiar interfaces, such as chatting and code completion, this interface\nruns your text data directly against the LLM. \n\nThe LLM's flexibility, allows for it to adapt to the subject of your data, and \nprovide surprisingly accurate predictions. This saves the data scientist the\nneed to write and tune an NLP model. \n\nIn recent times, the capabilities of LLMs that can run locally in your computer \nhave increased dramatically. This means that these sort of analysis can run\nin your machine with good accuracy. Additionally, it makes it possible to take\nadvantage of LLM's at your institution, since the data will not leave the\ncorporate network. \n\n## Get started\n\n- Install `mall` from Github\n\n \n::: {.panel-tabset group=\"language\"}\n## R\n```r\npak::pak(\"mlverse/mall/r\")\n```\n\n## Python\n```python\npip install \"mall @ git+https://git@github.com/mlverse/mall.git#subdirectory=python\"\n```\n:::\n\n- [Download Ollama from the official website](https://ollama.com/download)\n\n- Install and start Ollama in your computer\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n- Install Ollama in your machine. The `ollamar` package's website provides this\n[Installation guide](https://hauselin.github.io/ollama-r/#installation)\n\n- Download an LLM model. For example, I have been developing this package using\nLlama 3.2 to test. To get that model you can run: \n ```r\n ollamar::pull(\"llama3.2\")\n ```\n \n## Python\n\n- Install the official Ollama library\n ```python\n pip install ollama\n ```\n\n- Download an LLM model. For example, I have been developing this package using\nLlama 3.2 to test. To get that model you can run: \n ```python\n import ollama\n ollama.pull('llama3.2')\n ```\n:::\n \n#### With Databricks (R only)\n\nIf you pass a table connected to **Databricks** via `odbc`, `mall` will \nautomatically use Databricks' LLM instead of Ollama. *You won't need Ollama \ninstalled if you are using Databricks only.*\n\n`mall` will call the appropriate SQL AI function. For more information see our \n[Databricks article.](https://mlverse.github.io/mall/articles/databricks.html) \n\n## LLM functions\n\nWe will start with loading a very small data set contained in `mall`. It has\n3 product reviews that we will use as the source of our examples.\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(mall)\ndata(\"reviews\")\n\nreviews\n#> # A tibble: 3 × 1\n#> review \n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. \n#> 2 I regret buying this laptop. It is too slow and the keyboard is too noisy \n#> 3 Not sure how to feel about my new washing machine. Great color, but hard to f…\n```\n:::\n\n\n\n\n## Python\n\n\n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nimport mall \ndata = mall.MallData\nreviews = data.reviews\n\nreviews \n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
review
"This has been the best TV I've ever used. Great screen, and sound."
"I regret buying this laptop. It is too slow and the keyboard is too noisy"
"Not sure how to feel about my new washing machine. Great color, but hard to figure"
\n```\n\n:::\n:::\n\n\n\n:::\n\n\n\n\n\n\n\n\n\n### Sentiment\n\nAutomatically returns \"positive\", \"negative\", or \"neutral\" based on the text.\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_sentiment(review)\n#> # A tibble: 3 × 2\n#> review .sentiment\n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. positive \n#> 2 I regret buying this laptop. It is too slow and the keyboard is to… negative \n#> 3 Not sure how to feel about my new washing machine. Great color, bu… neutral\n```\n:::\n\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_sentiment.qmd) \n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.sentiment(\"review\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewsentiment
"This has been the best TV I've ever used. Great screen, and sound.""positive"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""negative"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""neutral"
\n```\n\n:::\n:::\n\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.sentiment) \n\n:::\n\n### Summarize\n\nThere may be a need to reduce the number of words in a given text. Typically to \nmake it easier to understand its intent. The function has an argument to \ncontrol the maximum number of words to output \n(`max_words`):\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_summarize(review, max_words = 5)\n#> # A tibble: 3 × 2\n#> review .summary \n#> \n#> 1 This has been the best TV I've ever used. Gr… it's a great tv \n#> 2 I regret buying this laptop. It is too slow … laptop purchase was a mistake \n#> 3 Not sure how to feel about my new washing ma… having mixed feelings about it\n```\n:::\n\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_summarize.qmd) \n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.summarize(\"review\", 5)\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewsummary
"This has been the best TV I've ever used. Great screen, and sound.""great tv with good features"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""laptop purchase was a mistake"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""feeling uncertain about new purchase"
\n```\n\n:::\n:::\n\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.summarize) \n\n:::\n\n### Classify\n\nUse the LLM to categorize the text into one of the options you provide: \n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_classify(review, c(\"appliance\", \"computer\"))\n#> # A tibble: 3 × 2\n#> review .classify\n#> \n#> 1 This has been the best TV I've ever used. Gr… computer \n#> 2 I regret buying this laptop. It is too slow … computer \n#> 3 Not sure how to feel about my new washing ma… appliance\n```\n:::\n\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_classify.qmd) \n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.classify(\"review\", [\"computer\", \"appliance\"])\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewclassify
"This has been the best TV I've ever used. Great screen, and sound.""appliance"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""computer"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""appliance"
\n```\n\n:::\n:::\n\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.classify) \n\n:::\n\n### Extract \n\nOne of the most interesting use cases Using natural language, we can tell the \nLLM to return a specific part of the text. In the following example, we request\nthat the LLM return the product being referred to. We do this by simply saying \n\"product\". The LLM understands what we *mean* by that word, and looks for that\nin the text.\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_extract(review, \"product\")\n#> # A tibble: 3 × 2\n#> review .extract \n#> \n#> 1 This has been the best TV I've ever used. Gr… tv \n#> 2 I regret buying this laptop. It is too slow … laptop \n#> 3 Not sure how to feel about my new washing ma… washing machine\n```\n:::\n\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_extract.qmd) \n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.extract(\"review\", \"product\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewextract
"This has been the best TV I've ever used. Great screen, and sound.""tv"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""laptop"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""washing machine"
\n```\n\n:::\n:::\n\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.extract) \n\n:::\n\n\n### Translate\n\nAs the title implies, this function will translate the text into a specified \nlanguage. What is really nice, it is that you don't need to specify the language\nof the source text. Only the target language needs to be defined. The translation\naccuracy will depend on the LLM\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_translate(review, \"spanish\")\n#> # A tibble: 3 × 2\n#> review .translation \n#> \n#> 1 This has been the best TV I've ever used. Gr… Esta ha sido la mejor televisió…\n#> 2 I regret buying this laptop. It is too slow … Me arrepiento de comprar este p…\n#> 3 Not sure how to feel about my new washing ma… No estoy seguro de cómo me sien…\n```\n:::\n\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_translate.qmd) \n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.translate(\"review\", \"spanish\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewtranslation
"This has been the best TV I've ever used. Great screen, and sound.""Esta ha sido la mejor televisión que he utilizado hasta ahora. Gran pantalla y sonido."
"I regret buying this laptop. It is too slow and the keyboard is too noisy""Me arrepiento de comprar este portátil. Es demasiado lento y la tecla es demasiado ruidosa."
"Not sure how to feel about my new washing machine. Great color, but hard to figure""No estoy seguro de cómo sentirme con mi nueva lavadora. Un color maravilloso, pero muy difícil de en…
\n```\n\n:::\n:::\n\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.translate) \n\n:::\n\n### Custom prompt\n\nIt is possible to pass your own prompt to the LLM, and have `mall` run it \nagainst each text entry:\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_prompt <- paste(\n \"Answer a question.\",\n \"Return only the answer, no explanation\",\n \"Acceptable answers are 'yes', 'no'\",\n \"Answer this about the following text, is this a happy customer?:\"\n)\n\nreviews |>\n llm_custom(review, my_prompt)\n#> # A tibble: 3 × 2\n#> review .pred\n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. Yes \n#> 2 I regret buying this laptop. It is too slow and the keyboard is too noi… No \n#> 3 Not sure how to feel about my new washing machine. Great color, but har… No\n```\n:::\n\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_custom.qmd) \n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nmy_prompt = (\n \"Answer a question.\"\n \"Return only the answer, no explanation\"\n \"Acceptable answers are 'yes', 'no'\"\n \"Answer this about the following text, is this a happy customer?:\"\n)\n\nreviews.llm.custom(\"review\", prompt = my_prompt)\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewcustom
"This has been the best TV I've ever used. Great screen, and sound.""Yes"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""No"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""No"
\n```\n\n:::\n:::\n\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.custom) \n\n:::\n\n## Model selection and settings\n\nYou can set the model and its options to use when calling the LLM. In this case,\nwe refer to options as model specific things that can be set, such as seed or\ntemperature. \n\n::: {.panel-tabset group=\"language\"}\n## R\n\nInvoking an `llm` function will automatically initialize a model selection\nif you don't have one selected yet. If there is only one option, it will \npre-select it for you. If there are more than one available models, then `mall`\nwill present you as menu selection so you can select which model you wish to \nuse.\n\nCalling `llm_use()` directly will let you specify the model and backend to use.\nYou can also setup additional arguments that will be passed down to the \nfunction that actually runs the prediction. In the case of Ollama, that function\nis [`chat()`](https://hauselin.github.io/ollama-r/reference/chat.html). \n\nThe model to use, and other options can be set for the current R session\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_use(\"ollama\", \"llama3.2\", seed = 100, temperature = 0)\n```\n:::\n\n\n\n\n\n## Python \n\nThe model and options to be used will be defined at the Polars data frame \nobject level. If not passed, the default model will be **llama3.2**.\n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.use(\"ollama\", \"llama3.2\", options = dict(seed = 100))\n```\n:::\n\n\n\n\n:::\n\n#### Results caching \n\nBy default `mall` caches the requests and corresponding results from a given\nLLM run. Each response is saved as individual JSON files. By default, the folder\nname is `_mall_cache`. The folder name can be customized, if needed. Also, the\ncaching can be turned off by setting the argument to empty (`\"\"`).\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_use(.cache = \"_my_cache\")\n```\n:::\n\n\n\n\nTo turn off:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_use(.cache = \"\")\n```\n:::\n\n\n\n\n## Python \n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.use(_cache = \"my_cache\")\n```\n:::\n\n\n\n\nTo turn off:\n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.use(_cache = \"\")\n```\n:::\n\n\n\n\n:::\n\nFor more information see the [Caching Results](articles/caching.qmd) article. \n\n## Key considerations\n\nThe main consideration is **cost**. Either, time cost, or money cost.\n\nIf using this method with an LLM locally available, the cost will be a long \nrunning time. Unless using a very specialized LLM, a given LLM is a general model. \nIt was fitted using a vast amount of data. So determining a response for each \nrow, takes longer than if using a manually created NLP model. The default model\nused in Ollama is [Llama 3.2](https://ollama.com/library/llama3.2), \nwhich was fitted using 3B parameters. \n\nIf using an external LLM service, the consideration will need to be for the \nbilling costs of using such service. Keep in mind that you will be sending a lot\nof data to be evaluated. \n\nAnother consideration is the novelty of this approach. Early tests are \nproviding encouraging results. But you, as an user, will still need to keep\nin mind that the predictions will not be infallible, so always check the output.\nAt this time, I think the best use for this method, is for a quick analysis.\n\n\n## Vector functions (R only)\n\n`mall` includes functions that expect a vector, instead of a table, to run the\npredictions. This should make it easier to test things, such as custom prompts\nor results of specific text. Each `llm_` function has a corresponding `llm_vec_`\nfunction:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_vec_sentiment(\"I am happy\")\n#> [1] \"positive\"\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_vec_translate(\"Este es el mejor dia!\", \"english\")\n#> [1] \"It's the best day!\"\n```\n:::\n", + "markdown": "---\nformat:\n html:\n toc: true\nexecute:\n eval: true\n freeze: true\n---\n\n\n\n\n\n\n\n\n\n\n[![R check](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml)\n[![Python tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml)\n[![R package coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main)\n[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)\n\n\n\n\nRun multiple LLM predictions against a data frame. The predictions are processed \nrow-wise over a specified column. It works using a pre-determined one-shot prompt,\nalong with the current row's content. `mall` has been implemented for both R\nand Python. The prompt that is use will depend of the type of analysis needed. \n\nCurrently, the included prompts perform the following: \n\n- [Sentiment analysis](#sentiment)\n- [Text summarizing](#summarize)\n- [Classify text](#classify)\n- [Extract one, or several](#extract), specific pieces information from the text\n- [Translate text](#translate)\n- [Verify that something it true](#verify) about the text (binary)\n- [Custom prompt](#custom-prompt)\n\nThis package is inspired by the SQL AI functions now offered by vendors such as\n[Databricks](https://docs.databricks.com/en/large-language-models/ai-functions.html) \nand Snowflake. `mall` uses [Ollama](https://ollama.com/) to interact with LLMs \ninstalled locally. \n\n\n\nFor **R**, that interaction takes place via the \n[`ollamar`](https://hauselin.github.io/ollama-r/) package. The functions are \ndesigned to easily work with piped commands, such as `dplyr`. \n\n```r\nreviews |>\n llm_sentiment(review)\n```\n\n\n\nFor **Python**, `mall` is a library extension to [Polars](https://pola.rs/). To\ninteract with Ollama, it uses the official\n[Python library](https://github.com/ollama/ollama-python).\n\n```python\nreviews.llm.sentiment(\"review\")\n```\n\n## Motivation\n\nWe want to new find ways to help data scientists use LLMs in their daily work. \nUnlike the familiar interfaces, such as chatting and code completion, this interface\nruns your text data directly against the LLM. \n\nThe LLM's flexibility, allows for it to adapt to the subject of your data, and \nprovide surprisingly accurate predictions. This saves the data scientist the\nneed to write and tune an NLP model. \n\nIn recent times, the capabilities of LLMs that can run locally in your computer \nhave increased dramatically. This means that these sort of analysis can run\nin your machine with good accuracy. Additionally, it makes it possible to take\nadvantage of LLM's at your institution, since the data will not leave the\ncorporate network. \n\n## Get started\n\n- Install `mall` from Github\n\n \n::: {.panel-tabset group=\"language\"}\n## R\n```r\npak::pak(\"mlverse/mall/r\")\n```\n\n## Python\n```python\npip install \"mall @ git+https://git@github.com/mlverse/mall.git#subdirectory=python\"\n```\n:::\n\n- [Download Ollama from the official website](https://ollama.com/download)\n\n- Install and start Ollama in your computer\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n- Install Ollama in your machine. The `ollamar` package's website provides this\n[Installation guide](https://hauselin.github.io/ollama-r/#installation)\n\n- Download an LLM model. For example, I have been developing this package using\nLlama 3.2 to test. To get that model you can run: \n ```r\n ollamar::pull(\"llama3.2\")\n ```\n \n## Python\n\n- Install the official Ollama library\n ```python\n pip install ollama\n ```\n\n- Download an LLM model. For example, I have been developing this package using\nLlama 3.2 to test. To get that model you can run: \n ```python\n import ollama\n ollama.pull('llama3.2')\n ```\n:::\n \n#### With Databricks (R only)\n\nIf you pass a table connected to **Databricks** via `odbc`, `mall` will \nautomatically use Databricks' LLM instead of Ollama. *You won't need Ollama \ninstalled if you are using Databricks only.*\n\n`mall` will call the appropriate SQL AI function. For more information see our \n[Databricks article.](https://mlverse.github.io/mall/articles/databricks.html) \n\n## LLM functions\n\nWe will start with loading a very small data set contained in `mall`. It has\n3 product reviews that we will use as the source of our examples.\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(mall)\ndata(\"reviews\")\n\nreviews\n#> # A tibble: 3 × 1\n#> review \n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. \n#> 2 I regret buying this laptop. It is too slow and the keyboard is too noisy \n#> 3 Not sure how to feel about my new washing machine. Great color, but hard to f…\n```\n:::\n\n\n\n## Python\n\n\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nimport mall \ndata = mall.MallData\nreviews = data.reviews\n\nreviews \n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
review
"This has been the best TV I've ever used. Great screen, and sound."
"I regret buying this laptop. It is too slow and the keyboard is too noisy"
"Not sure how to feel about my new washing machine. Great color, but hard to figure"
\n```\n\n:::\n:::\n\n\n:::\n\n\n\n\n\n\n\n### Sentiment\n\nAutomatically returns \"positive\", \"negative\", or \"neutral\" based on the text.\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_sentiment(review)\n#> # A tibble: 3 × 2\n#> review .sentiment\n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. positive \n#> 2 I regret buying this laptop. It is too slow and the keyboard is to… negative \n#> 3 Not sure how to feel about my new washing machine. Great color, bu… neutral\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_sentiment.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.sentiment(\"review\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewsentiment
"This has been the best TV I've ever used. Great screen, and sound.""positive"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""negative"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""neutral"
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.sentiment) \n\n:::\n\n### Summarize\n\nThere may be a need to reduce the number of words in a given text. Typically to \nmake it easier to understand its intent. The function has an argument to \ncontrol the maximum number of words to output \n(`max_words`):\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_summarize(review, max_words = 5)\n#> # A tibble: 3 × 2\n#> review .summary \n#> \n#> 1 This has been the best TV I've ever used. Gr… it's a great tv \n#> 2 I regret buying this laptop. It is too slow … laptop purchase was a mistake \n#> 3 Not sure how to feel about my new washing ma… having mixed feelings about it\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_summarize.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.summarize(\"review\", 5)\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewsummary
"This has been the best TV I've ever used. Great screen, and sound.""great tv with good features"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""laptop purchase was a mistake"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""feeling uncertain about new purchase"
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.summarize) \n\n:::\n\n### Classify\n\nUse the LLM to categorize the text into one of the options you provide: \n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_classify(review, c(\"appliance\", \"computer\"))\n#> # A tibble: 3 × 2\n#> review .classify\n#> \n#> 1 This has been the best TV I've ever used. Gr… computer \n#> 2 I regret buying this laptop. It is too slow … computer \n#> 3 Not sure how to feel about my new washing ma… appliance\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_classify.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.classify(\"review\", [\"computer\", \"appliance\"])\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewclassify
"This has been the best TV I've ever used. Great screen, and sound.""appliance"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""computer"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""appliance"
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.classify) \n\n:::\n\n### Extract \n\nOne of the most interesting use cases Using natural language, we can tell the \nLLM to return a specific part of the text. In the following example, we request\nthat the LLM return the product being referred to. We do this by simply saying \n\"product\". The LLM understands what we *mean* by that word, and looks for that\nin the text.\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_extract(review, \"product\")\n#> # A tibble: 3 × 2\n#> review .extract \n#> \n#> 1 This has been the best TV I've ever used. Gr… tv \n#> 2 I regret buying this laptop. It is too slow … laptop \n#> 3 Not sure how to feel about my new washing ma… washing machine\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_extract.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.extract(\"review\", \"product\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewextract
"This has been the best TV I've ever used. Great screen, and sound.""tv"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""laptop"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""washing machine"
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.extract) \n\n:::\n\n### Classify\n\nUse the LLM to categorize the text into one of the options you provide: \n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_classify(review, c(\"appliance\", \"computer\"))\n#> # A tibble: 3 × 2\n#> review .classify\n#> \n#> 1 This has been the best TV I've ever used. Gr… computer \n#> 2 I regret buying this laptop. It is too slow … computer \n#> 3 Not sure how to feel about my new washing ma… appliance\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_classify.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.classify(\"review\", [\"computer\", \"appliance\"])\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewclassify
"This has been the best TV I've ever used. Great screen, and sound.""appliance"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""computer"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""appliance"
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.classify) \n\n:::\n\n### Verify \n\nThis functions allows you to check and see if a statement is true, based\non the provided text. By default, it will return a 1 for \"yes\", and 0 for\n\"no\". This can be customized.\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_verify(review, \"is the customer happy with the purchase\")\n#> # A tibble: 3 × 2\n#> review .verify\n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. 1 \n#> 2 I regret buying this laptop. It is too slow and the keyboard is too n… 0 \n#> 3 Not sure how to feel about my new washing machine. Great color, but h… 0\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_verify.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.verify(\"review\", \"is the customer happy with the purchase\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewverify
"This has been the best TV I've ever used. Great screen, and sound."1
"I regret buying this laptop. It is too slow and the keyboard is too noisy"0
"Not sure how to feel about my new washing machine. Great color, but hard to figure"0
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.verify) \n\n:::\n\n\n\n### Translate\n\nAs the title implies, this function will translate the text into a specified \nlanguage. What is really nice, it is that you don't need to specify the language\nof the source text. Only the target language needs to be defined. The translation\naccuracy will depend on the LLM\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nreviews |>\n llm_translate(review, \"spanish\")\n#> # A tibble: 3 × 2\n#> review .translation \n#> \n#> 1 This has been the best TV I've ever used. Gr… Esta ha sido la mejor televisió…\n#> 2 I regret buying this laptop. It is too slow … Me arrepiento de comprar este p…\n#> 3 Not sure how to feel about my new washing ma… No estoy seguro de cómo me sien…\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_translate.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.translate(\"review\", \"spanish\")\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewtranslation
"This has been the best TV I've ever used. Great screen, and sound.""Esta ha sido la mejor televisión que he utilizado hasta ahora. Gran pantalla y sonido."
"I regret buying this laptop. It is too slow and the keyboard is too noisy""Me arrepiento de comprar este portátil. Es demasiado lento y la tecla es demasiado ruidosa."
"Not sure how to feel about my new washing machine. Great color, but hard to figure""No estoy seguro de cómo sentirme con mi nueva lavadora. Un color maravilloso, pero muy difícil de en…
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.translate) \n\n:::\n\n### Custom prompt\n\nIt is possible to pass your own prompt to the LLM, and have `mall` run it \nagainst each text entry:\n\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_prompt <- paste(\n \"Answer a question.\",\n \"Return only the answer, no explanation\",\n \"Acceptable answers are 'yes', 'no'\",\n \"Answer this about the following text, is this a happy customer?:\"\n)\n\nreviews |>\n llm_custom(review, my_prompt)\n#> # A tibble: 3 × 2\n#> review .pred\n#> \n#> 1 This has been the best TV I've ever used. Great screen, and sound. Yes \n#> 2 I regret buying this laptop. It is too slow and the keyboard is too noi… No \n#> 3 Not sure how to feel about my new washing machine. Great color, but har… No\n```\n:::\n\n\n\nFor more information and examples visit this function's \n[R reference page](reference/llm_custom.qmd) \n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nmy_prompt = (\n \"Answer a question.\"\n \"Return only the answer, no explanation\"\n \"Acceptable answers are 'yes', 'no'\"\n \"Answer this about the following text, is this a happy customer?:\"\n)\n\nreviews.llm.custom(\"review\", prompt = my_prompt)\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
reviewcustom
"This has been the best TV I've ever used. Great screen, and sound.""Yes"
"I regret buying this laptop. It is too slow and the keyboard is too noisy""No"
"Not sure how to feel about my new washing machine. Great color, but hard to figure""No"
\n```\n\n:::\n:::\n\n\n\nFor more information and examples visit this function's \n[Python reference page](reference/MallFrame.qmd#mall.MallFrame.custom) \n\n:::\n\n## Model selection and settings\n\nYou can set the model and its options to use when calling the LLM. In this case,\nwe refer to options as model specific things that can be set, such as seed or\ntemperature. \n\n::: {.panel-tabset group=\"language\"}\n## R\n\nInvoking an `llm` function will automatically initialize a model selection\nif you don't have one selected yet. If there is only one option, it will \npre-select it for you. If there are more than one available models, then `mall`\nwill present you as menu selection so you can select which model you wish to \nuse.\n\nCalling `llm_use()` directly will let you specify the model and backend to use.\nYou can also setup additional arguments that will be passed down to the \nfunction that actually runs the prediction. In the case of Ollama, that function\nis [`chat()`](https://hauselin.github.io/ollama-r/reference/chat.html). \n\nThe model to use, and other options can be set for the current R session\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_use(\"ollama\", \"llama3.2\", seed = 100, temperature = 0)\n```\n:::\n\n\n\n\n## Python \n\nThe model and options to be used will be defined at the Polars data frame \nobject level. If not passed, the default model will be **llama3.2**.\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.use(\"ollama\", \"llama3.2\", options = dict(seed = 100))\n```\n:::\n\n\n\n:::\n\n#### Results caching \n\nBy default `mall` caches the requests and corresponding results from a given\nLLM run. Each response is saved as individual JSON files. By default, the folder\nname is `_mall_cache`. The folder name can be customized, if needed. Also, the\ncaching can be turned off by setting the argument to empty (`\"\"`).\n\n::: {.panel-tabset group=\"language\"}\n## R\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_use(.cache = \"_my_cache\")\n```\n:::\n\n\n\nTo turn off:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_use(.cache = \"\")\n```\n:::\n\n\n\n## Python \n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.use(_cache = \"my_cache\")\n```\n:::\n\n\n\nTo turn off:\n\n\n\n::: {.cell}\n\n```{.python .cell-code}\nreviews.llm.use(_cache = \"\")\n```\n:::\n\n\n\n:::\n\nFor more information see the [Caching Results](articles/caching.qmd) article. \n\n## Key considerations\n\nThe main consideration is **cost**. Either, time cost, or money cost.\n\nIf using this method with an LLM locally available, the cost will be a long \nrunning time. Unless using a very specialized LLM, a given LLM is a general model. \nIt was fitted using a vast amount of data. So determining a response for each \nrow, takes longer than if using a manually created NLP model. The default model\nused in Ollama is [Llama 3.2](https://ollama.com/library/llama3.2), \nwhich was fitted using 3B parameters. \n\nIf using an external LLM service, the consideration will need to be for the \nbilling costs of using such service. Keep in mind that you will be sending a lot\nof data to be evaluated. \n\nAnother consideration is the novelty of this approach. Early tests are \nproviding encouraging results. But you, as an user, will still need to keep\nin mind that the predictions will not be infallible, so always check the output.\nAt this time, I think the best use for this method, is for a quick analysis.\n\n\n## Vector functions (R only)\n\n`mall` includes functions that expect a vector, instead of a table, to run the\npredictions. This should make it easier to test things, such as custom prompts\nor results of specific text. Each `llm_` function has a corresponding `llm_vec_`\nfunction:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_vec_sentiment(\"I am happy\")\n#> [1] \"positive\"\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nllm_vec_translate(\"Este es el mejor dia!\", \"english\")\n#> [1] \"It's the best day!\"\n```\n:::\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/index.qmd b/index.qmd index 0c32a34..8929777 100644 --- a/index.qmd +++ b/index.qmd @@ -32,6 +32,7 @@ mall::llm_use("ollama", "llama3.2", seed = 100, .cache = "_readme_cache") + Run multiple LLM predictions against a data frame. The predictions are processed row-wise over a specified column. It works using a pre-determined one-shot prompt, along with the current row's content. `mall` has been implemented for both R @@ -44,6 +45,7 @@ Currently, the included prompts perform the following: - [Classify text](#classify) - [Extract one, or several](#extract), specific pieces information from the text - [Translate text](#translate) +- [Verify that something it true](#verify) about the text (binary) - [Custom prompt](#custom-prompt) This package is inspired by the SQL AI functions now offered by vendors such as @@ -298,6 +300,63 @@ For more information and examples visit this function's ::: +### Classify + +Use the LLM to categorize the text into one of the options you provide: + + +::: {.panel-tabset group="language"} +## R + +```{r} +reviews |> + llm_classify(review, c("appliance", "computer")) +``` + +For more information and examples visit this function's +[R reference page](reference/llm_classify.qmd) + +## Python + +```{python} +reviews.llm.classify("review", ["computer", "appliance"]) +``` + +For more information and examples visit this function's +[Python reference page](reference/MallFrame.qmd#mall.MallFrame.classify) + +::: + +### Verify + +This functions allows you to check and see if a statement is true, based +on the provided text. By default, it will return a 1 for "yes", and 0 for +"no". This can be customized. + + +::: {.panel-tabset group="language"} +## R + +```{r} +reviews |> + llm_verify(review, "is the customer happy with the purchase") +``` + +For more information and examples visit this function's +[R reference page](reference/llm_verify.qmd) + +## Python + +```{python} +reviews.llm.verify("review", "is the customer happy with the purchase") +``` + +For more information and examples visit this function's +[Python reference page](reference/MallFrame.qmd#mall.MallFrame.verify) + +::: + + ### Translate @@ -486,4 +545,3 @@ llm_vec_sentiment("I am happy") ```{r} llm_vec_translate("Este es el mejor dia!", "english") ``` - From 8b79e5f315b2ee4da9d3ceb69e39b875ce1ee50f Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Wed, 16 Oct 2024 13:42:51 -0500 Subject: [PATCH 03/11] Updates Python README --- .gitignore | 3 + python/README.md | 388 +++++++++++++++++++++++++++++++++++++++------- python/README.qmd | 222 +++++++++++++++++++++++--- 3 files changed, 541 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index d22d76c..2d6e554 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,6 @@ docs/ python/mall/src/ python/assets/style.css + +python/README_files +python/README.html diff --git a/python/README.md b/python/README.md index 18194a4..d191eb8 100644 --- a/python/README.md +++ b/python/README.md @@ -1,100 +1,384 @@ -# mall -## Intro + + + + + +[![Python +tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml) +[![Code +coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main) +[![Lifecycle: +experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) + Run multiple LLM predictions against a data frame. The predictions are processed row-wise over a specified column. It works using a pre-determined one-shot prompt, along with the current row’s content. +`mall` has been implemented for both R and Python. The prompt that is +use will depend of the type of analysis needed. + +Currently, the included prompts perform the following: + +- [Sentiment analysis](#sentiment) +- [Text summarizing](#summarize) +- [Classify text](#classify) +- [Extract one, or several](#extract), specific pieces information from + the text +- [Translate text](#translate) +- [Verify that something it true](#verify) about the text (binary) +- [Custom prompt](#custom-prompt) + +This package is inspired by the SQL AI functions now offered by vendors +such as +[Databricks](https://docs.databricks.com/en/large-language-models/ai-functions.html) +and Snowflake. `mall` uses [Ollama](https://ollama.com/) to interact +with LLMs installed locally. + +For **Python**, `mall` is a library extension to +[Polars](https://pola.rs/). To interact with Ollama, it uses the +official [Python library](https://github.com/ollama/ollama-python). + +``` python +reviews.llm.sentiment("review") +``` -## Install +## Motivation -To install from Github, use: +We want to new find ways to help data scientists use LLMs in their daily +work. Unlike the familiar interfaces, such as chatting and code +completion, this interface runs your text data directly against the LLM. + +The LLM’s flexibility, allows for it to adapt to the subject of your +data, and provide surprisingly accurate predictions. This saves the data +scientist the need to write and tune an NLP model. + +In recent times, the capabilities of LLMs that can run locally in your +computer have increased dramatically. This means that these sort of +analysis can run in your machine with good accuracy. Additionally, it +makes it possible to take advantage of LLM’s at your institution, since +the data will not leave the corporate network. + +## Get started + +- Install `mall` from Github ``` python -pip install "mall @ git+https://git@github.com/edgararuiz/mall.git@python#subdirectory=python" +pip install "mall @ git+https://git@github.com/mlverse/mall.git#subdirectory=python" ``` -## Examples +- [Download Ollama from the official + website](https://ollama.com/download) + +- Install and start Ollama in your computer + +- Install the official Ollama library + + ``` python + pip install ollama + ``` + +- Download an LLM model. For example, I have been developing this + package using Llama 3.2 to test. To get that model you can run: + + ``` python + import ollama + ollama.pull('llama3.2') + ``` + +## LLM functions + +We will start with loading a very small data set contained in `mall`. It +has 3 product reviews that we will use as the source of our examples. ``` python import mall -import polars as pl - -reviews = pl.DataFrame( - data=[ - "This has been the best TV I've ever used. Great screen, and sound.", - "I regret buying this laptop. It is too slow and the keyboard is too noisy", - "Not sure how to feel about my new washing machine. Great color, but hard to figure" - ], - schema=[("review", pl.String)], -) +data = mall.MallData +reviews = data.reviews + +reviews ``` -## Sentiment +
+ +| review | +|----| +| "This has been the best TV I've ever used. Great screen, and sound." | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | +
+

+ +### Sentiment + +Automatically returns “positive”, “negative”, or “neutral” based on the +text. ``` python reviews.llm.sentiment("review") ``` -shape: (3, 2) +

+ +| review | sentiment | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "positive" | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "negative" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "neutral" | -| review | sentiment | -|----------------------------------|------------| -| str | str | -| "This has been the best TV I've… | "positive" | -| "I regret buying this laptop. I… | "negative" | -| "Not sure how to feel about my … | "neutral" | +
-## Summarize +### Summarize + +There may be a need to reduce the number of words in a given text. +Typically to make it easier to understand its intent. The function has +an argument to control the maximum number of words to output +(`max_words`): ``` python reviews.llm.summarize("review", 5) ``` -shape: (3, 2) +
+ +| review | summary | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "great tv with good features" | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "laptop purchase was a mistake" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "feeling uncertain about new purchase" | + +
-| review | summary | -|----------------------------------|----------------------------------| -| str | str | -| "This has been the best TV I've… | "it's a great tv" | -| "I regret buying this laptop. I… | "laptop not worth the money" | -| "Not sure how to feel about my … | "feeling uncertain about new pu… | +### Classify -## Translate (as in ‘English to French’) +Use the LLM to categorize the text into one of the options you provide: ``` python -reviews.llm.translate("review", "spanish") +reviews.llm.classify("review", ["computer", "appliance"]) ``` -shape: (3, 2) +
+ +| review | classify | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "appliance" | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "computer" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "appliance" | + +
+ +### Extract + +One of the most interesting use cases Using natural language, we can +tell the LLM to return a specific part of the text. In the following +example, we request that the LLM return the product being referred to. +We do this by simply saying “product”. The LLM understands what we +*mean* by that word, and looks for that in the text. + +``` python +reviews.llm.extract("review", "product") +``` + +
+ +| review | extract | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "tv" | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "laptop" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "washing machine" | + +
-| review | translation | -|----------------------------------|----------------------------------| -| str | str | -| "This has been the best TV I've… | "Esta ha sido la mejor TV que h… | -| "I regret buying this laptop. I… | "Lo lamento comprar este portát… | -| "Not sure how to feel about my … | "No estoy seguro de cómo sentir… | +### Classify -## Classify +Use the LLM to categorize the text into one of the options you provide: ``` python reviews.llm.classify("review", ["computer", "appliance"]) ``` -shape: (3, 2) +
-| review | classify | -|----------------------------------|-------------| -| str | str | -| "This has been the best TV I've… | "appliance" | -| "I regret buying this laptop. I… | "appliance" | -| "Not sure how to feel about my … | "appliance" | +| review | classify | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "appliance" | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "computer" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "appliance" | -## LLM session setup +
+ +### Verify + +This functions allows you to check and see if a statement is true, based +on the provided text. By default, it will return a 1 for “yes”, and 0 +for “no”. This can be customized. + +``` python +reviews.llm.verify("review", "is the customer happy with the purchase") +``` + +
+ +| review | verify | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | 1 | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | 0 | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | 0 | + +
+ +### Translate + +As the title implies, this function will translate the text into a +specified language. What is really nice, it is that you don’t need to +specify the language of the source text. Only the target language needs +to be defined. The translation accuracy will depend on the LLM + +``` python +reviews.llm.translate("review", "spanish") +``` + +
+ +| review | translation | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "Esta ha sido la mejor televisión que he utilizado hasta ahora. Gran pantalla y sonido." | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "Me arrepiento de comprar este portátil. Es demasiado lento y la tecla es demasiado ruidosa." | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "No estoy seguro de cómo sentirme con mi nueva lavadora. Un color maravilloso, pero muy difícil de en… | + +
+ +### Custom prompt + +It is possible to pass your own prompt to the LLM, and have `mall` run +it against each text entry: ``` python -reviews.llm.use(options = dict(seed = 100)) +my_prompt = ( + "Answer a question." + "Return only the answer, no explanation" + "Acceptable answers are 'yes', 'no'" + "Answer this about the following text, is this a happy customer?:" +) + +reviews.llm.custom("review", prompt = my_prompt) ``` - {'backend': 'ollama', 'model': 'llama3.2', 'options': {'seed': 100}} +
+ +| review | custom | +|----|----| +| "This has been the best TV I've ever used. Great screen, and sound." | "Yes" | +| "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "No" | +| "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "No" | + +
+ +## Model selection and settings + +You can set the model and its options to use when calling the LLM. In +this case, we refer to options as model specific things that can be set, +such as seed or temperature. + +The model and options to be used will be defined at the Polars data +frame object level. If not passed, the default model will be +**llama3.2**. + +``` python +reviews.llm.use("ollama", "llama3.2", options = dict(seed = 100)) +``` + +#### Results caching + +By default `mall` caches the requests and corresponding results from a +given LLM run. Each response is saved as individual JSON files. By +default, the folder name is `_mall_cache`. The folder name can be +customized, if needed. Also, the caching can be turned off by setting +the argument to empty (`""`). + +``` python +reviews.llm.use(_cache = "my_cache") +``` + +To turn off: + +``` python +reviews.llm.use(_cache = "") +``` + +## Key considerations + +The main consideration is **cost**. Either, time cost, or money cost. + +If using this method with an LLM locally available, the cost will be a +long running time. Unless using a very specialized LLM, a given LLM is a +general model. It was fitted using a vast amount of data. So determining +a response for each row, takes longer than if using a manually created +NLP model. The default model used in Ollama is [Llama +3.2](https://ollama.com/library/llama3.2), which was fitted using 3B +parameters. + +If using an external LLM service, the consideration will need to be for +the billing costs of using such service. Keep in mind that you will be +sending a lot of data to be evaluated. + +Another consideration is the novelty of this approach. Early tests are +providing encouraging results. But you, as an user, will still need to +keep in mind that the predictions will not be infallible, so always +check the output. At this time, I think the best use for this method, is +for a quick analysis. diff --git a/python/README.qmd b/python/README.qmd index 862f56a..68cf93a 100644 --- a/python/README.qmd +++ b/python/README.qmd @@ -1,71 +1,253 @@ --- format: gfm +execute: + eval: true --- -# mall + -## Intro + +[![Python tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml) +[![Code coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main) +[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) + -Run multiple LLM predictions against a data frame. The predictions are processed row-wise over a specified column. It works using a pre-determined one-shot prompt, along with the current row’s content. -## Install -To install from Github, use: +Run multiple LLM predictions against a data frame. The predictions are processed +row-wise over a specified column. It works using a pre-determined one-shot prompt, +along with the current row's content. `mall` has been implemented for both R +and Python. The prompt that is use will depend of the type of analysis needed. + +Currently, the included prompts perform the following: + +- [Sentiment analysis](#sentiment) +- [Text summarizing](#summarize) +- [Classify text](#classify) +- [Extract one, or several](#extract), specific pieces information from the text +- [Translate text](#translate) +- [Verify that something it true](#verify) about the text (binary) +- [Custom prompt](#custom-prompt) + +This package is inspired by the SQL AI functions now offered by vendors such as +[Databricks](https://docs.databricks.com/en/large-language-models/ai-functions.html) +and Snowflake. `mall` uses [Ollama](https://ollama.com/) to interact with LLMs +installed locally. + +For **Python**, `mall` is a library extension to [Polars](https://pola.rs/). To +interact with Ollama, it uses the official +[Python library](https://github.com/ollama/ollama-python). + +```python +reviews.llm.sentiment("review") +``` + +## Motivation + +We want to new find ways to help data scientists use LLMs in their daily work. +Unlike the familiar interfaces, such as chatting and code completion, this interface +runs your text data directly against the LLM. + +The LLM's flexibility, allows for it to adapt to the subject of your data, and +provide surprisingly accurate predictions. This saves the data scientist the +need to write and tune an NLP model. + +In recent times, the capabilities of LLMs that can run locally in your computer +have increased dramatically. This means that these sort of analysis can run +in your machine with good accuracy. Additionally, it makes it possible to take +advantage of LLM's at your institution, since the data will not leave the +corporate network. + +## Get started + +- Install `mall` from Github + ```python -pip install "mall @ git+https://git@github.com/edgararuiz/mall.git@python#subdirectory=python" +pip install "mall @ git+https://git@github.com/mlverse/mall.git#subdirectory=python" ``` -## Examples +- [Download Ollama from the official website](https://ollama.com/download) + +- Install and start Ollama in your computer + +- Install the official Ollama library + ```python + pip install ollama + ``` + +- Download an LLM model. For example, I have been developing this package using +Llama 3.2 to test. To get that model you can run: + ```python + import ollama + ollama.pull('llama3.2') + ``` +## LLM functions + +We will start with loading a very small data set contained in `mall`. It has +3 product reviews that we will use as the source of our examples. + ```{python} #| include: false import polars as pl -from polars.dataframe._html import HTMLFormatter -html_formatter = get_ipython().display_formatter.formatters['text/html'] -html_formatter.for_type(pl.DataFrame, lambda df: "\n".join(HTMLFormatter(df).render())) +pl.Config(fmt_str_lengths=100) +pl.Config.set_tbl_hide_dataframe_shape(True) +pl.Config.set_tbl_hide_column_data_types(True) ``` - ```{python} import mall -import polars as pl data = mall.MallData reviews = data.reviews + +reviews ``` + ```{python} #| include: false -reviews.llm.use(options = dict(seed = 100)) +reviews.llm.use(options = dict(seed = 100), _cache = "_readme_cache") ``` +

-## Sentiment +### Sentiment + +Automatically returns "positive", "negative", or "neutral" based on the text. ```{python} reviews.llm.sentiment("review") ``` -## Summarize +### Summarize + +There may be a need to reduce the number of words in a given text. Typically to +make it easier to understand its intent. The function has an argument to +control the maximum number of words to output +(`max_words`): + ```{python} reviews.llm.summarize("review", 5) ``` -## Translate (as in 'English to French') +### Classify + +Use the LLM to categorize the text into one of the options you provide: + ```{python} -reviews.llm.translate("review", "spanish") +reviews.llm.classify("review", ["computer", "appliance"]) ``` -## Classify +### Extract + +One of the most interesting use cases Using natural language, we can tell the +LLM to return a specific part of the text. In the following example, we request +that the LLM return the product being referred to. We do this by simply saying +"product". The LLM understands what we *mean* by that word, and looks for that +in the text. + +```{python} +reviews.llm.extract("review", "product") +``` + +### Classify + +Use the LLM to categorize the text into one of the options you provide: ```{python} reviews.llm.classify("review", ["computer", "appliance"]) ``` -## LLM session setup +### Verify + +This functions allows you to check and see if a statement is true, based +on the provided text. By default, it will return a 1 for "yes", and 0 for +"no". This can be customized. + +```{python} +reviews.llm.verify("review", "is the customer happy with the purchase") +``` + + +### Translate + +As the title implies, this function will translate the text into a specified +language. What is really nice, it is that you don't need to specify the language +of the source text. Only the target language needs to be defined. The translation +accuracy will depend on the LLM + +```{python} +reviews.llm.translate("review", "spanish") +``` + +### Custom prompt + +It is possible to pass your own prompt to the LLM, and have `mall` run it +against each text entry: + +```{python} +my_prompt = ( + "Answer a question." + "Return only the answer, no explanation" + "Acceptable answers are 'yes', 'no'" + "Answer this about the following text, is this a happy customer?:" +) + +reviews.llm.custom("review", prompt = my_prompt) +``` + +## Model selection and settings + +You can set the model and its options to use when calling the LLM. In this case, +we refer to options as model specific things that can be set, such as seed or +temperature. + +The model and options to be used will be defined at the Polars data frame +object level. If not passed, the default model will be **llama3.2**. ```{python} -reviews.llm.use(options = dict(seed = 100)) +#| eval: false +reviews.llm.use("ollama", "llama3.2", options = dict(seed = 100)) ``` + +#### Results caching + +By default `mall` caches the requests and corresponding results from a given +LLM run. Each response is saved as individual JSON files. By default, the folder +name is `_mall_cache`. The folder name can be customized, if needed. Also, the +caching can be turned off by setting the argument to empty (`""`). + +```{python} +#| eval: false +reviews.llm.use(_cache = "my_cache") +``` + +To turn off: + +```{python} +#| eval: false +reviews.llm.use(_cache = "") +``` + +## Key considerations + +The main consideration is **cost**. Either, time cost, or money cost. + +If using this method with an LLM locally available, the cost will be a long +running time. Unless using a very specialized LLM, a given LLM is a general model. +It was fitted using a vast amount of data. So determining a response for each +row, takes longer than if using a manually created NLP model. The default model +used in Ollama is [Llama 3.2](https://ollama.com/library/llama3.2), +which was fitted using 3B parameters. + +If using an external LLM service, the consideration will need to be for the +billing costs of using such service. Keep in mind that you will be sending a lot +of data to be evaluated. + +Another consideration is the novelty of this approach. Early tests are +providing encouraging results. But you, as an user, will still need to keep +in mind that the predictions will not be infallible, so always check the output. +At this time, I think the best use for this method, is for a quick analysis. From 688dbcb8d2bd5c580a64a772bd166462d59ab57b Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Wed, 16 Oct 2024 13:51:37 -0500 Subject: [PATCH 04/11] Changes logo location --- python/README.md | 2 +- python/README.qmd | 2 +- python/logo.png | Bin 0 -> 29279 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 python/logo.png diff --git a/python/README.md b/python/README.md index d191eb8..8f3604c 100644 --- a/python/README.md +++ b/python/README.md @@ -1,6 +1,6 @@ - + diff --git a/python/README.qmd b/python/README.qmd index 68cf93a..8a7cb4c 100644 --- a/python/README.qmd +++ b/python/README.qmd @@ -4,7 +4,7 @@ execute: eval: true --- - + [![Python tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml) diff --git a/python/logo.png b/python/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ebeea89fc91c9f9a97a8af0ed81bbc569b05ca6f GIT binary patch literal 29279 zcmV*bKvchpP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x00(qQO+^Rj2^SCoIfn>$;s5|~07*naRCwC${b{r%*;VHW z{`Rrqj&FYceD;`UDHNJO5<(P%nuLO;$V?<9nUGAPdaYUuMdpWEy}By_MRsQmy;dh# zy}G&)Vul0-QI0KE0DY74yN^6f z3{+rRl=lNat?0LSzTaprEU<9$1k;Q2B>jl>iRt~#C`Bz zy#ef-l{Wx=6Y}Us9|j14cOd+fs(!zSS**`YvvA@Bvquk8Yt{&SgQCPy%Ffyr>*p`9 zyS7Fe#|iKl!p8;vgM;-Cy`imdO5OnU4alR9{0xAnDz}TsKSTA0Rpj8}?9%cUc zF`6??g1|#eR>hLnv~^0q6R~w=mG$!%=xlG}tlbsiKZE>|!e<0JANbi{c%>inH7RcZ z`b9{_UMMIF2>*>LKMB0k4?}G#42Y&7ijs0Te4M1O~K;agGtu)=p4&zo+}&9QLu1hY$r zsMIQWo@BMEiin^HsA4?H+JbGxAXo&FZg2l z;2SjGb;%omzJ`3_qYopBr-nC)^5csBkcb?sHk-_!IL`dBqtu%Ew2n#2U)0myY_dQI3WR=Xc5U`PB z_n8(u&vvkprLqvR@Zb#fW3@4DUAzRrT1ThVWn+1Tjq?`@nr|KCPY^y1?lF)Eln?&w z|N2T-(W_G40Q3ZT^pSr7=nU}Pz=u`!y`Jw^rxzDlICYZg#d(4-7yx;wl`}vmt(5I2 zc4?pMqG|S+3u8F83L)URA}b7-sICV)ni%{F*ns4w7GoK?8_-30rGhtev~S?&>OO9LEA*Lgbf$ zKTb*4JUl-52Guu^HvkQM;*p232ID*KMEMa#e^^9r4eNC?fBYB=$B$8KHZWd^xzNGK zK(lt2`)q%GmuNFaa#}-GvjL(pgYV)AQ@1vle{h=0bdZ}Lqkt|TZk^I?_t;p@X}+y3 zoVB|M|3#2r1pTxc*A?sWMtsOOIG`VWlG-hY; zeQ#)5zzCob(A3dB+hhC57HKCP0dr8V5Kh(UuJ54MWxwSE!rIXai*K2wcBry1pmX(2 znp#@hZPqSaX7l1j`UTC0{6503f<6wU-{@}iZ%9Bt`pCnmrige=koN&Ut*YN{Ja4+W zFwdcrCz(F9fFA}KzC?zm1q=ag!1gV>&$iipu7itpz&EQZ)wwB7eCq==j?J_3*mJCY z^#xpFM*v@)51D_<49yeuY_JG>09`O5iW2uNTkBh_og2`6R^SPQ-vAy3xd`Yts$<4` zr3>(KOU@_zBJvQ(f3K?lXAyZveP*U|`1ad5dgtv-&ClZb-T<7EkKD={x)}UzeSMeK z=`NaTu6-4NspE^B`mVPSO~qVUKf}`9w^46S(_P)h#+eou5z?New;Ch9P?-x5Z>VDg z85wkn2tnY{n5xrUn5WWc;;b`Czkk>{_dXHc1`?rM{Gso7SM+i0Y4OM#FjY$1-z7aC(ijsqx%G|*(Up`-pMHYgW~-#B!K()`brJbf;Nwp93j)yxzk%40*FB&g{piDJ_LQ^0_o93l z^xa+%RA!bAv3T+%&G}i3=N0%Ja>Ek5 zp107Loni6hNv4+$5rn}|^W|*ABtU0_Kc#)9%kI-{((c&cuTkaLH{VPB?s+a;cmj=f zS(u+C3BQYE){b)Jv(K^h>_yb-$eXAgsc`6_S?Wisd%TKSu|`c1 z0Cmvo#cZx@uzEI+=8K|OM82%>%b@>ZYARe=U5WXz|LOIsv0s;fe)OY11B$_zyFvfC zD*r@~TdK{*z+D~IYLu0u$W;Js1lzZ?p6$?jzJn%O0+OS)FwKegy%{}ZxOn#KgeGNv zZU*1?hZUW3%dK{YwT&I77f*2F$USU+@jNSEejb~QHjv6}$l^mYOx@ZT@ZK4S%iFoP zks-)4Z)2aeODn9MJzvm#9fZFS_!#gvK=<{^hP`Ht1^zd`E6xe24M9#`MAhi>FS~Tv)(( z1{n*YxE7$J^_ZQlbxo)O{KP<|KRzks#8UV#0Y2lS(lJPbmB;~|8fP}Lt4W0vaEQ?hXK z1ha<^q!f#uqOHhBbGZ@JITlWo$SbZBCz;)Onk-+Cvr51n9nf|MfzKPt-a>MR9~}ze)N%tv+x0J}a>`c+G)B_U?PB-Z{(p zbH(6qhA<3Ayoc8Wbm1&f$M#N(&7CfbM{j4bd4ko?J7lXu&Ss^m_L{DH3qXoiEo&Y3PQaU%n@`r-`7 z-}eA+9xlA_I2GSAH#?2*d3(Y~uLsaYF;HlCx~y$%)0jQN@uT;!{gq`_zVsYvG)fIt zW&#!;nq}tpCdQNN($E9Y=YF3kO4(lDVfE}qHZCm_b-Rk{IT8N7!f&EH2iVslfBl*Q z^zZ%r!#JTP@GjuPihhsr%xq(Bj>VHFnOa=Hcx7hcIsu)3rn@P-Pqpb?>Su$?93YA^ zb83lW?|Xoj+2-=Ordxrtm67#NM?4J=_Q2oYL#YtY2Pd_3Q<9R#u8hyI%(R6@fo;&aH`ve(>jC zJHUQT0Q%8<)`&CQC-9@H`XLcHQEfJ5;ly!fj~v0TREqJ?@Zqi(&?xbG!tN6-;*Df* zzN%t8!~DG`IsERsSlzwA=9M$d%`|D&YZE<)Hw5Usix7c0Nm$?5B1%0@-1=txZNtS+ zJx*tJd&CJNzA$@tlS6NrC1?gCG~@LEy0}!DI@-HE)-JBFdiFf+%}t!OZQzp#9|Jxq zNarhjg@5z2fBi~B;hOTQ26Q&N+6n|mfPbXQkAprKR;&K(kt58XI7X$>1oE)VJU(LZ zaMu8|6obD@eOgbqNp{l`XcQ9$9DduK%)aF~7cW0UZ+nG>eDGIP-hlv`VSqn?b(XEI z9d_C=M^4?#l+LsK=_lDayE0%g2A;)}b(Y>fM{S|KQmJx--+sftMES0_v_P#7FjrXg8Izd zAP{++63afzPdR+CQrf49qjRQ5`?(IbKQ{QAu5tW5_Y>Ya#kn(&qe+X!xtYwf$nd@g z3h3fGc|+T6wOQY4F}HLpOEb5y`uWqWKYkWx$87thDobylqj{p1@1Ha%+#k^8YxR2x z8!KB}Idh)vQKb%LJt9Q;3lf?fy~&x*QK3MG>oOo2cL5|wot)@1}PsRkR*=XtqxbtF0*#-d_nVV3t2QD!e>#UR}-jT zaX^3KU;m5Dnc>_b@O{8fs_HxZFs#lVUSj_EaVpKJK~aY^6&Yt7Id;CdOS~GtxTg_D z>}Byh7VbaAp>MjKmF@Fvt)63Ux=y2B-#@_rWdgJSNt&{;xlJd6q;Jc0Srl%I>nLlxy#{4`O1G&4mG<#_PXxDe_Jhnr! zkqoG<7bDrk)qtaKzl-Syk8%FuQ~BU;hHABvSJEpC==`Cbb8Kzzvfb)&=)^tDm_?R9 z{S@1$FXt}B(oL@C+4KL8y?c?vZ<`-z=(0DnK#4k~(~enNUgydS=L(uH75ED9%ZU7u z2P-M?!B^0Y{)zzl(T{!vz(=?r_~)wpkcgb9O-;%C@#9P{En$Kndq*YrTbVN+0J`Vc z`QnWMT2-k`*E#kr_Y<6GaQ4jCA?dI%KSSXA15@M`*U;rwtgzc|v%a~@^ukG&W^ZHd z@6WLI_!%^@lkKak)d9+}Z_WWN`vbbLwbRtHyWM5w{0ggQ&(Yr6!lh{&kxzma(R{5} zY9{*23g|~4c^LITEXRN!%9FReIjmK^xg$rIJ$jU&))*=n&1tr)0@||k#a)uk7Z2$A zp;=D6?*XEzl=G**PQ`O$gFjw%fR?O@)9dwEUEd<8&vX3P{j{E3W%={Zkaqho;_YXJ zI`++r9De&X06jAE0pciSb8VL^XD+dR{sR4W3sqea;Sb3)_E%BTmpka`Wdn3Rmv08) z{R%&Y>ihg4s7);{Fn{6%)v0N8pa-M`rt1LoW4pLE9C^oG)DKM;)B3Ld;6|t{HreQ{ zuzc=mW*Q;WQ&Xe!2VaGNF4s>S$E>Yyky7ROsRyvjG3#GHi%rsNzgktIoepuk$FXl- z;PBfQUKF4OaH{mW3G0_Px%|R;wl7~M?)5D2oQV87@Ck5dLGS^J;%{g_cJ$hG&}wpqE5N^ zsmEFQ(rJ!<(;`RSF81a6H1x+sseN^7^r%J~(poH<8pZ4H~IkqDndO-A3;RKIlT+yhgJ2TiO6l0M%~OEJ;wCm!+1euU|tN^B9W0DQu=Si zfdXKJXg6Z_i!HS4=-rZ%Hr+@-&Yts1`kk0~rV;ukZe|Twt{gMFs5B|-+ zB#GUk2;UF-Vc_jSB@AblmY6+yjG*4g)6B9jc=9@>w093sM1+1TqV>gH=tJ$nI{u=+ zulKNgO%Wjs0zA)L7nsZU9l6fve`EWvAIxRoitODt5zM9ig%hUT?%>po^lBsJ&#!N- zWA|%4l5;i(^!(&HyB3&BKjm%#`u&uR%Udj;xxnVdOGKTnf}R8U1n}$5>9d|O_5(ln zZx0-M2MXxlf8=4hQPK!}?^_Xm8s)n@-txbLmDm5Ujs}=O(^a0;9Kjzf^V~}IjW>e;l9;H6Ni17pB#3I;h zyfh{NZ~~}v4V0!SNtz-Sk~BdQliRPQm%ZoOlYlmSY2~he?~9zIIfo}*7bwTnNErxa z6|=9&aTTCnwcbkSCrL_bv${)?gg8k@uTc&cDE}uTw;2qgM{b|rr&{+pcH1Euvkk7C zn`iCZ1;4ws^)~11-6G~6%uO|ZDNd9B0<`~^|I&ajM0l5+90tF)i*bNKD^ z9DeKEgmO(Xw{-8bTt@?oBFpxAo7MBn*-BuY4kD}?G5_^SW)}4QPW7jq)AwLZy*e{PxOfPu)Ui%;^Fk(8?0zkK(*Dvb%Ih)*bSI|$WCw|b zsRtSa^SQ~f_cqrA*1pjDq-E$F#-aNy?{WHaBS$?=O%Tcswk_hooANNB+2i^(qo-V7MNB|fv8CgOjh&nuRuj)H zm&q#!4(%f;LbSIjl_a}HRTOXRr|~0gUN*?Qq){uy^({fuC#VNG5H+(y_bHeHsmes} zddXVBm-0Itc9|l=v3G32l8#8?Kq^D+=Bbjc7P$A}6(Jqdvg5N~%9_bnW%4V{o-Y-_sN(N)jLo&KL9aT9 zQ8GXS_~;(n8#0|Sa?Cz&)#b+-6hHr6ouV#V&`6A6H3O?B;7=g46-ZRFaMcNov8bao z&%Nf4AES@}6$3xCjka8Vfs3inm*WaYJq9M&n;S|Y1_C9bojK8dv$&tviM zG>eC3*gSEV%cn1}e&NDw((C;b;9{HY#|0VAi92XO=f$q6YEYk86r2p}WzeHPv7RnC&LqveC<$(FmX+ z4h&b0JG!bY?L-{k?a@3L5C+46OAqCYDiLrhP>##!=(GUq3d)lyR8ig>TBgO8ZkBoQ zE-IXkmAZku0;MS>&=)qoK$<8|$1v-`eHDggK*?Ef=GR&PE^EjxYw+ zPYK-7q_!ThaC&p36^bhDT8RJVCeAm^UEJk?#Tmlc3Yyoq0WQYE7rIdMga>9kW{eOg zmN-`0QOfhFW7B5NAZs1qsMV{Sy6q5ii_<)9;L_920yG3OVtKwqfX*M#7*q`8AXYNF zta!=B7=pm3TB%T}gap2iF@{hBa6;e*G%B@`fke{vA#?;@7U>rFKJ~CV$!x^)4W7wn zW>kWJMzuz>Q733*J%kYu6}+B@IH6Xl&?pqP(QNvV1~h7Qv@!&KAlb>jcvMYgkL8{i zAIbmusZTAeQEOHQ$9Mwuo+2j2^Rn_Zj3Y==JkOA*AblYpVC0y|LwG%5X0}GHQRQaj zKzQi#3o9xi^?bUO8X8teg8)AVbe#I&AT}OtzdAx~*@{PZN=Yi&6sx2X(4DSNyjScS zY`#=PGF2y?o1#Bcqwl5JO9XnF*|RFp^#WR6Nz>5;4nb&%M^Ks*SOB$+ z;;>j?SNt)GQ3k;8Vo_2O3_JK>44rz7?(`7QaW${^(5E+6?*jBg59bL^gkHVMju#RU z495NjXsN}KlB%$yem0{%f973LlF{M*AnZm<+czTz3h43+xe_+m3aS_pL=BQ=rS2)H zvpky!=hLy<`yTF&kiKtl&hgxQmGd*<-h1T1JiXA6qI~&SgQo%yBE|L0-~~9K&k#6O=vYTj;7_(-GzvA-)rmr6l{E{0w82P5qO$ZII1A5qV0mIBr_ghXv+!gK z&UPonTjVd+0=sGUH&G(%_|3C@Jjpb$bAoeK+NF@~ zNw4FEc;qsKZu0y+QgqdY9anx^P8~M2q^TuwZj?6#)G8V)i7%v{5IKw0BHic6lulHH zxbI;RdRS~sp0vDlKOk|6Q;3b=_F=!QLShvI#GXt9MVcSuM2Nh-v|=H#&F*Q85P1{0 zTp)HzYL(aszPJA?v_O(7C`5rwq~%hDG|%u9`7#moNM5egAx4Ok!Y=gjr@}!4ijsu1 z+a(MG{N`RoVpXyI2%B1B=P>w)U%Zo?DG`Hn1|tSxG@UTTfK^A_GY2X+4rtkbrvgWl zE)Tj6^=gGM3^B%#rYT>FQoho&oRpM1{Q)bQWuo7XNtaX3%vQKC6R?zaIcj@@srJR6 z5lF@3S*fvu$K594SZD~naD+9996Z*8mW6v8a42BNa+JFEtrR}rRRRNVoifzT=rbCG zwM6-R*HQD}ozosQGj6_Q@GNkBhUMf%kM*uw4&LO*B~Zt#OK9R#!Gb!vX#`$KFx6la z$JfWYb{8l*t@M1M>Koe3a)HUgLyC~9P*GSh%67p|mBayw4}rpI56+Kh=;4>tQ#>!5 zy7+aEk%3(CGLsPe86_6@swewu=zc}1`mk-_3&js(wqdUVHD6ix;PXy~{f>+tYf+fa z8v9ofymUgjP|S>CfiF6P=k}Hfpl_7woU-EypNR!jaSr^=Z6uFDZ(FF%&9f|qzuMT| z^G>Z(l6nKbQsvy7vML_Z9X7{ojFi&pMI5OI9H|->Du#Y+=_-V=QhDjH47`MZhRn&3 z5i{Vdr5`868sJp|Vjvnn!3T0u!zath*t*K~h0IOc&HpAIlbZ+aYm`;;Sy#JPIVPWn z!1gt2)ble3!sdSW%8!{?2V)N?dC$Ln9mj0JXlA96!h=`I%C(`xeWb-30z~jD6?#A~ zNk`8woP9xn2?9DmhvBIJ>Of;iq8MvataCUOoPrxY^d*xUr|n+Ev8q{2N1CQsaWCo` zFXu6@#cQc5I^k%!-s2_>BCn$W`wi%9HLzhK0n}en=`y;-7rRe61 zZD8mR7&~q-1}W{IbnMNFl4r4cg0c zX`L!Irz%|@17L*wI5S{y3I${HKbHghreBYaZj3VLd!M9C;gwzXH!3#_Xib#lY(&~~ zq&6i^VxlM_oeSVN*p{Qe65&+^8(30j>390rLp7-1=w06s$iVCyL&Y;xJ)h7s7|GUD zNmYrgrJq_-mzj5gF;skyis#|yjD}Suww5UWOI!xgJT~+V6@8!MaDKOiQ=2(J z1}S!LTm;yR9Dj1YM=kKM&XJ~;er(uC1)Q>Zy2XWOE%jQBYFHugJ;2bu(qrYg6U_(#Jd;HkW>eqfKq7=xpq4uB zU5lAc9I>_ZlZcj$*_f`2y0>kpvVPhi5r9 zH&3%xC-g&7n^M``rWV*&jM90fWk>)_V1%zzJ!ML57zVK4F{VV*m7^fX#q7gHALKI+ z;y0wc_iE25H%#x|AfS~Lha7JZ)|yC?;QD>iPKdYDhE9YxQ>8jLMXlMS5>`mlgxaQ$ zzp;}IcVEg{9Iv+hw2n-6K46Zqhwj5?uA6U)NEIe1>owXS`$Ov{JO zI>+99oH-K^B{BVekL`YsYDEzv%-wyQ<5Hnst5c~|@_2cVjkOnOoxYf_$nfGzbwhv_ zggEN)wJ&^zBz2_L5+@1$exFN?IXHBR^_6A5{PbmN)e2z{kXp-5r_1Kd5oF=WK~>*X zWe{UjEWMUX;ZP{HN6xt(2ZUZdgkVYLy@iFvie%avfgahJ6+{1ZV{+ zVaW8{bPnj0D2nNY9&14eD3!p^GT_&$13(*5DuH(+x4fzhyV$&ZI`obe&WCch>q<_A zvO}Tls4#F2hpFM^kYn><$7=FX5*9jP1M0}69tbRhTY9#Chs z8TH#-c>eF%^^`cZ^!t5wdog`;2xsc7UwNK$8wvG#Et}C4_=pG_XD{QdyWz%r!#1=r zhFY~s=&~3M&-3#95Lq~(1|iN&VQW>=z|Z115{EMf>ut}xU4!8SA$5IfVi&2X!|6N;X?1R+6Ys zuj`Rkg@e%1N@h|NPKKpMom10OZ1*F!`+Y=&n(uS8+2F+V6!Wzzq34lW$9BKZrFNSO zyDfI37^jLS8DD#HW{MM2Q_NH=1jZ0s$5y}3rQH@6S}nHw5#7`h*_6~}9=CBR8dAkG z)P0}1YK2>7X1HT+mSa;*rYjXHxdKR>qZLJ5YPETObBingNz@-thm`|w9 z#HrFv63%Y#@Rchotaf@Fs@Hhy(Zk#_J3}J~1~aiC18pacIk&ybW0$Y+{MI%*k?1qGpa5C6nY+Y->2^T+%-SPOr=8TdCb zjx6ylr%rIFe(m5DAuH2^N0vA-J;j%=tgzbd@X*o2+`BkW;vDtBCom=h$^q2hOCwp5 z_ob@GkKNs5uHq3VDZPHoPN&a*+B7W3ui64$_?DB$+380tHtJb#p;?fTZw%^631?oV5*RpK^*B`Zh$c?ic9yF#ZC_<8? z1d(8TOPFMr3<8fvHKf_d4E{8=^wSVOJ^1cs0OhO2m*n)AxlT|N@xwtvjuPQTCD?H$@t%uKbyt+TT< zE2HrTMugkvW?87!=p-qg2#p}%zQuV?&P?x9j)c>jTUlB9eXJ^tAmI3PlY15xSZLI* zS}zCc(R;}x)5X?0cDMT&Ux<<%(A|hs1Jnd`J26|aqu+;KCB^r%RB>@^Zz`nZ=E6sZ z<~e9p&AU$?`nsW0#?Ed&ok)_OjN|OCbaM85yJx&UD{P z+gNWN(EH39UbX>$)i(sGhcQYTrL?zGE*lSd8K+=%l7!tjrWGdxKv#@MD~fQrO+#ISCR_c8-8iP3T5LWm+K7<4 zEPLUZogJQD-{4&*j$JqF@@f9d(%BIvS%0rJm!B~N| z)nh02AjxvsrF~1ko#NNRmvzR98@R3=k+WHbCYR+b-uHOx@;{3~NUYu0%`JIshKFIM zQo);$M{K>@W24ujo2GdqaoP1WODzWAB`Ga0;rz}nZ(2IUO!dYYl2?QD96Z^}$b&3< zikGJX6MS`}!Cwccv~O|>RuP>GMSOTsw&PyHeHBYJ4DdXUGzr+Q1U#J@GGEurCt0x1 za3*v)_-9-)kCoyy1D#&OR4Qs9fG5V)}VLzhXvqUV#JlrHC)ti6T02{F(>! zj3>NjTB!SwrfKF}i4&ft$}ZmC_kTd+s{^y4rSz%5g0Vap#!NMQDxr|19&65Ltz}r_ z`qI+|}57dLOHU4=_3z>tMo3j422^4gW5!zk0Td#Web+`ch`5LYRkH%LH9mbAR) z`x7<&Yf%OX%^Ce0R?flS^X338*SYU?E=p!LbdzM_jFC(d%=T9sF2`a~|5y&5XnK8sBvMC?{uTZm<-uVR^N%9M@CM z@}D`_vcD>%%u;OhdR*LXu~6UZ!agxQ#dqFziZ5PX;oSBP?IcC>SdPQZI&VF4n0pr& z2wubd^N7gg4*I2j|2H2#I^x$J7S}=MDI{Aq!RC3_UT@h)3zH*pj#ixT)Y=BO&&}?M zv=b3d&djh_ud~|eveoaCI>%HPa=6i8rdqkKpsCkN?WtxUy`utVT5Tphpjx+B4-^5RX48wK;r-75;PYvGeEs1%;Uv_h;tTT+Icmd-dBxv7NbMYf$RtWiMBAd+rV zeYRpCE^^yBqL#(0@$%-jo4^-c3mAHx`(T)Mz&a&S$96yB3zsi5SF3W@{M-!(8aY)` z>j?az6XHfA(5~ls4_fng9X!zk<7IOSGf!dGa|h%T3&rI4G1;HFd86c}reH~B9)bpv z#EA4Ut^@rp8l{{F1MY8TGkSf`BZ*^nwwgTEjSkeYq9qUDq_?7!7X}@A0{2TOGf2!{ zn(MaTwX^FNEJrpQoCGelI{fw7^9XSJ-0X{v%Sf!{?Dh^D-7fbX%H80~9`EIT?)aii zMXbuz@{hZ#)WwZS=4+U%gnaugCuoFWmbWF2>Gu14wbfxeRVvK|8dEiDjRw_9g&+tp zM%Y=~y{X1^V}LFyz;7azIYeT__85%+_$vIbQoS! zTRT-Yx;>s;Tj%-BEfVYKrIvS}JdTKz(k={hcuc+y!)Gp@XTS27$2JED>}C+~&ZR@l z*Q#00mTrfQmf?kNkL^_Ot5Z~_W~nwBRI62jAjBBKuN2XWFJtB;DDG8M!ek#{;X!lC zNM4D9l0uut&eBd|w)%Z4V;t~K6lKZ3)eT(Bk#+1w+3~)Zf$Pqp>_!ohEn+URo}sihUi#CDjc!>Q6s)6Chjv&(K2@xt~FcQ4Fya(asCN`)X#P!2;6<5n-5 zRPgl1CYM`n`gu5MD~YKFKDW%w(2f&gm*wmb>u4o0TfP1s-g_tN(@#?pmnn{3nz9o` z?DV7IyHn_=DeXAU!Cy*Ab^s%^K`-ITaWSJWZ*=L3{Z>1AadKd^-F9EOuxZ)sW(C@f zloz%f&Dary7MnV@wj7;UIgqpxIZJQzjP*}%Y_ZjQikk1?8H45->Nool?Ig*xXNFoL zYx&ZZ6)x=VQuTd2k->G=dp&w-noWKs)4<&{gcDI)owRmAat08 zNu5Iz#X3i19jz$g%=Qj*)e4LC8ne|3RXQ|IWVX_gz>+QIW4 zgop^eq|K98zC<&CBuVM_`n0+c?RW;&Di^oU;6;{W3#Yifat{I5%1C;Bav<^0*1?%= z$C(YwhE)il6)7)7mRir^hZbiYtzD(%IFM0c(Uz?`VhbC+KCL(=$TJqHLaG^{5|_QO z0&sn6S?%@Mj^aUXWCarIh@B<2S$D3vw(6&Fxzk~z*C+6@Jb8sFQ)E@LrQCE5Nb(?~ z)MX8&lO}BSBQCVs_@#M`4kd9)l4oYh?(MMZ25B0)X_{+1265C*VWSt(j+6WuByWIu z=&Q4&E+fe_Nw~1vX2t7{EJ=}+GC~ckmz&kj0wRXK>+)RdSt?#knpom~Ot0tYcyB?y zkSqOj^ph1f-4zbc9;H@2m`M!>kj5Zi;4&Q@EsvNNMUFQ=z*vuroh;>DDzi6vt}D6f z@1=Vyq&S>~*x5nqta47eVaRCyf^)z`*g^ie;`m}skZc?%**beJYaFpSBrPVJ4X<6y z${BL;8K|8DD~?2=Zylb=%IBFZ7^z6cQ>=~R)aBRG;eznua@-K} z%{*&;wuW|KgspDMW+!1QZ4WhpVR3ahl7MW(&sVr zZ-U)3NM#OOgz+0(vQ5r*9mj&0Tf1L|q%&YtiuNlgU8(a#_!dOqP4QQ$y58Vku67iA z=v{Q2$8*~|_(r&Uc81Ux6k)5o!B(^~@O?$D%62kE#fzEtTUoHZD)n%Rqf@7d(mt13 zZC1LD=`dlgVo6=XOm&W_$}H>MD+FGXi!DoRE%z_ZlcYQJ(k@HQW9;-d!6;L+NBCNl zV7xe27Q^}(8E3sj9-^1b^&N|2mxxFWzD*D@q>_-T#YPdH@o4x4&j`nZFsIVsWTnDx zdzZA=V`g@m8TAM~LnAb-wh|6C18T9MZ!KH>7;7Uc+Mw>6kvb9}Rl)UQQkPKC1`XdZ z9}35UVR~H=Vbi8O)$8MXa4bE~?CzIFJX%H_m;IwWUb_wBwm9DV8g=cAdc9PUut6lV zJd;*wxHh+@XYu4#bmt4O_zp-rNN|{)_7?r_47JJ@CwIP#BrV1@f@s!=o~Yjnz_IS* z%-Ef5$q02=?j!cz#g+s;CBeB>xDjN+E-3g z4ClMcB;77aZ;IJUh2x=RrL{sc6pjYdbkjBFD@~T;1e^3Y@JssZr1e-^kp_LUDah-NNqY;zc5Smk{xB8;(6JCCAE$qAH5fG#MWlN ztCCY>8FMi-8}>?ZOtJb?7I!>)jA}M5OMs9V(Y;-u!y_id;0TGCsA9y!nktD?JRE^o zFcnC9*}B9y5RcTRq-yX*@ik&Ub)^{Me6SP}BjtHE;-RLRbo{lWZC>_{Jx-Nj}o`rBiH#94c;b<2f+s$S%_z-^=Vi2}v-n_gA3qe*^iKK=gs1{e|ns z=z&Dr8M4$*BNtYosy@c^Fvc4IuIEMc(uk@TQZtn?n=RY!+CF_3Q}ZfRO=aYM!wyVo z+a9U2H2oT(@%P}up|sO3Rvk^hPG~%u6`|@6D_MYkYPs0y;)`(7vsBw>P%SUvtaMD1 zbN=mAG~!fxi9nM*Fcg$cv&eFIk~zD<(extXTxKujrim!5`G>jUA7|07a3o#cw{9a- z;jDirf^ti`OvOccf-1o^PT`tM^sVJ$tAl3@C!39da_A>Lw)^Wi$r@_xLVC7FATc$s zS26_zAwf|hTcPi&)O<_D^sp`+c!3p%)fCbOict4^c#_eRf~Cls?QnE$6A?$v>tsEF z(`?-0AU0FE=P*y>AOYS1rdYn|MT1cA4BR?rFo_}Q2+Ii^^8y~MO;fKos8p*2VTcjo zD_g5Pwtj{C=H_|(!jZw~d;n90FRWkYYg?8H>S(diXL((2x z$)G%+I)1rrSTe#tZCYkbIswq|nMnDAuE%|K_=j~%FkwQ~C;Q5O=o%iZ!}r%y%-Hi4 zjbp|7{9-%n={_)TS@M!>Q0RrEhfd;V?q<2$<5!=4p1SYxfxB*JHq117H)?Zs)6z@3 z7=d$LkI!!zj#UG`X&$^0KxYc#sg}>zc06ut!h1dtkG=Ls8uUe!Ysk}m5v30!{Is)H8*opBm*iQ#tt2_CGZxi`;c+4E6$Mfj{t(H4(8yvMlL^hwfN zvBCWmlXV0L&^$r9r7<;6?a)6&ouzP`8neh8{kS?>mePpAQ^H9YF+gCd?AaAHIvAVh6{Bx(wYG zp}TWnx`ilV4y`#~S!`^9?P5fzx;lSP$e}RhTc$d>CLcyg8iamg_}Y$g*jL_Bhb;a= z1`U5bHhgJ2`+k2tv^l8dg%3#AI|92xn4ZUTGr&R7?p?_zkru$n(wFp{Pp^L$=oGg76lt=OyLT<9!(mBnh^FsG z-4u4`izJ<8qQ-lO^;QsX0O)={%HuUsT(X4~$-t@(&AU?@r`dNGWBXUIsmqVCv^p^x z+Y!|e+T9Au6#ecF8&|gQ^74{{A9b{Qv#2i8Yj3dJUtzA1GF?yeMA|kV^jXrR3hqrP zO1kr9Qm>VD?dm`i)L$a1EDiYYUawDRv)pi!Y*5|&490GuVsO!Iz`M|R4ZHQFY(lT( zDPb|F7_@&oa2LJy3LD9VLGT%3Fu;zhktVm})MI!39GiYW1GEUK6{>;YYBkUkU@fk5 z8RsQv>>x^z$Rh3(5a8&4yQ_#I7BSgL6A5^AWAaZ zG)^qr?SytunV)ilygI55BU$1V&!mK2l!L+Lz;=i_67|u1z}Jf$yEY4$A0BIItHs)^ z2k7-3En|s@h`lyhu81snryIrDdehd?YIg{OV%_U3Rj$F&?Pdce>m2P)gcyseLvZZ0 zW6+dF)gz3vuH5q+VQBHOxhBubt!nX;EPq?zSVFvLH>^IuHu7N7YW-PAyS+3+m22z!IOkIb;NN>lxWF;V-WEW$(BSu zC^>*Rg~ghA9>(}NsNu3}^0)C(JS%vS$u)JIWl$YW^yYC35+uPd1b26LO>lR(xVyUs zclY3K!3pl}1id)H-Qn`TZ|&ArZPo6l`7%9I-P3)#rq6SJPq5>+ib9l`M*`O($|SXl z=IC*FHQsbC<tYknYLA=+^HL5&MxSG3fNrg5%m`@2;|4XN z!5tIV&Mb?XTYr5SlQ)7YuE!v{JnI{EPN&dcg|NXs4YId%H*p%V1{@zEAD*Lfs_+Tf zmf!uy{;9$;M@`|C1C1e#_Vnc_7QYbu1cS>}8tgswz^khHd7orh-!3;_k!7T7L*VZC zFa@(&h4NQ7N>%n%>M{L43?#@!w18i~?1=Hrkhp{L=o9)eQ?$oU?NI`8leEEIe(R%` zP=08Z?SWc;;9qVrS;f!eP3^a1!Sa4D)39NR7t_jpX48aa6zE*dX{W*%duHQc&she7K)FD|#M1Fc0+j2HIW2ub~%|aMV1< z0ok7UXFA&Ig0-y{{A8pj3LRLOSI?Wy;h1qv6mv}t`so>XE`Z?-^ySuaYjeM?x zIZ6*b`2|CIlh~8MvhH6OSanN0=_&3f(ZFk%99G1yioT=`L{g}Em*RNNpcG2uZ1*H- z)!09H2-9jZs+4lX_j0tVl2+T)ZOS#eFM`dVDaC3`B34M0?Cd3~qQ6qb4j_!w@0|OD zFE*%}#9mVXynp}JdpOf^+iQfnSMmBaxSOL=C1Yiz+44Ms5MFXlGmQ|kboCNP@^_a< zji7kRN2!UMM@kR3=W$R(Jl;~577a-rRKqS2Dg8&sMBnt%sR-ni`bHDa<);CA1 zIgDp6e$YY%Ckj9FsLV>Fc6bWQ%FBWPx@9#E8z}bG{hVA(%cY2~i%^_mpu_C^yCY4U z-HSrZxE1|uD!AFvG(>RfTwYpOLkI6Gfin8rZre>6@oJg1?CF=?iq=Ct%S;v=Un^x! z$O&f}OshLY>D3x5Fu6iYL5iVb9xY4Q51=0^ski^q65d`_-D-B2t9p z1Gx}pZm)bXK(}lo*%_jNf9VnbiPj}a&wBx+U}B&_F6|TAE+;l@1w&d`78NUSnA{J$ z)K(?m3Wh{}&6Zo$ZjST;M|!t|N-6G93~KUvX6H~)DNCuoX`Y`@r_d|(m8N-5O+24o zzoPe4d51J21C+D8=1$b*);51x%gq?etxTBkO7y)zml_OsH4DZr6mwQKLjgmGM&W12 z*Sy;pO<0^nc41E3(ydl8NiXf7$kz?HnmP?0A7S_qU04HV_jhWCQ|eq;oQ;lMhde!> z%Apn6sml-7%Fb|Tf9G!3Gf|?d(iMuDq7GWi=h!rWP&u;Ik+6}(`Qd(N5kYbJTvI}s z20|hGa-b?*Ed3r(u|%W1-m!Qosh`rDNJaNT&@7%em?UD)M1n;1}+^oZ47Q=Ykm zhfqKlS}2|yvz2D-L$IpxnNtfi5dLlYll&ec8=4(V|LlmCFrq#3D6FG*d+F39+z|D5 zKe{0Cm@)5$1{c#}R+;V@a<|W+tNod)&EJn7`>Sac%bEND3GPBXBH|j3dlquKZrgP; zA{tj^&(5ZmUNRMj0Ye2Ma!{lJ&+vFr7SnMYQmj%}-?`|$kpi&@e&30AgR-i#*C+f? z0KuBy8R@4J=bI5)?mw@?cuFaK5lBvNS6ddn0W!X)3Fnh)7o<#>6eGSx#4QhOKbO1- zMpxX&j7^3JhSb@9jh@p}O|v}^d~hu+w5t^q#D{6E&U7J_U$v#3zHcmGo*`VBHAmXI zao_4Z9fi92g6PVtvmO&@+UxRF$LmUPA!qg0v>-jFjU?~afW77RK6ePLM2STFVX9$Z z4UfB{0kXvNjEZfFDPBMtnoNYxi0v`Ma7=JMlgkL(O?452B<5*gOcm&i{WaEBaxIcB z({GUrn+z=?=q{x9C1}N=|53m|{H8DeC!sAyJSS{{9CA5)K}0%RQ4fZQUdOV;^1ji& z2q(u7-4@$}SYk|bf*vqFAx#oEI;Ure!-)AoSG10e+e%L|p_H9Uf2L!D|F!m3)(;#Q zlxd`hAtE4$n>K?Fr7lg_*PDXhx;iq!a;Qge=dFp^PH>R+aSS!u*vf=B61&<@nf`S3 zf;vMClqyEOV6~X4!ktxtaeaoD0Vh#vD)5%OIV!Zz)1d#tv$eKwjDhm0_93?3m1T}? z0M&hiO<;o|BVJiN8E%Oz z8&|FDBS#!tMs%LG^b{eL<*6UxO5bQ7ln$+%Y>Mqn|Ew!4yk4NrFo`8m%B$5eW^4iZ zE1O+%PYmSf$MVERP&$&X*fjj#XNXe34{76O+evujC*jgG(0t%?Ia81HSR6<(#4!D= z3@rk-sb7%oY%>ak&ubthqKDw}+=W%cE%utq@wna8c3G8hfQL$&f|a2kXB2+;8+Ak* zTld2sYU?&TPAv-clL{RUaD8PS0e(sgsw2ac)13VD$)d0@jIErrCoLwMjC>`?xrhej zK$ei2gZ>Ag6mMf9B~nR#KMa7noGUd$>yZw@E&GCI1`{DDb#Ts#jUj^mcc11ANSuK$ z{zKf#h}}S&lF)k+eOdv(DH`z$|5Lx|>;>Z0JB7;~s>UMWCHT=vG#A02&8?Pt+!fZdd(Iz#NI-nSR^cQy#NT3tp zM4g-~hW#CrXkb_mK%)TlNWmDOuj9QR8*8Ts)o~_h-!}XNr=&=*5Chw8>k%K=SzS_h z%Ub&lkpoz=&G^Sy@)K{M-2+q@tqSVX!{32q1j?Z${L?H18Af8A+7!QDU%{vXQ}H8y zJopEmWMu?mpb?<8Y2;6=c&D~BLKg754Duj(1FJJ5PRePk9`4?Vo?Yi6|3$@wAC$hi z$U`LT91(lt(OY(M$i#$lj3wfC1k#eko>9lnMK-fy6S*%iXC>RU%Evx(_a^k#!Qp^2 zkRm@ir=vMmd`LT_wz#Qoc9fJaNH(*)Ssql!-@Z$sWQaEEO*|6!5EC-YnV3Ylu*RD} ziTF?jH@oM?wK$te2C&`}e7o}h(&RvCShH)E53DWmqLKyhD=*Jt|7-R;x6CaL8<~o` zp4siTzjUiB%{9bXNEChqOo%)v!;5EYO&o@wz59KzPEz=+qn=$u!>VME)KMjq`^5E2 z(QT}kTwGKZ{gu@_{R@s{c19KGm~nvZ<*l`Ip<^>~{@z#^#EMo)URVpf+oWK@@Nasi zjOwlWwXuTqO{or0W-q%QBHEZwAQu#Kfkk37uz@TCKm*)z{j|*w@ zQ&QAyM7A;cm~cyN%!woZ#14s&CSdX2_{l|Fw%vF+jac`K0xJ$63{^cw_}$77y!nMi z07?(^JDkbc{LU^nF^#t#f$I12ukAuCgjsrBV)BAjpVlBXQrk3hCjCpV-Gkfh2RZAn zGE-;KwXn!dO4S#k6X;C_-)jX`XB1BKt2VF$*h%|B_i#8>B~(FErcJh59f?1}#?`<= zRKx?|*XAV2b+@Td7b?1QWU6WC3}S91OKA0&%^`{sS|v?>u;hf$XFO`|2Y#P?vM<;A zE5T7IJ!iUP#H$3cQeXN{7)0#|7Uvx5fO{7-_%6iEjyEnil#70~tVE0Yej8eJJqG71 zRXAEBwJx!$wJ&EdRz0c0tFaZQgs`r)Ua^})$rqvXKP(GZKL6=vUa7?y&mB-l#}jt1 z7nk;k$$nJvkKl6*J+$+6tp2&ZLZH@9`pU@W>b`Q5NyD~-?@B~iB#C!=jj$}gdPjJ+ zFlU3icRMe{C~V=?##T6ztyb$yjB`MP>*V>ORm6N!=BmJjJ~sX1mC;_x-X3K+(3tR~ zYs^j^!KbzicA>#gN@Y0?cbjg7U}>nxBY!lJrrl0kqH(O)pxyOUEPY?$2FoYaEVL1p zs<&>+=<$Qy4b9DTs74z%} zE30p>>`WVC@KAT-JdK*`*alrb=WOp@JiSl+9}TKu*k6ShCP@rFHnvG8cO8fY#r<@IO9xARIO^=5sZfaw#_mGFa+>n+vC?C{YY&8YEC623tq|j;I zszUanZ%|)U`#xF!yjIRW(^V67E?d-cqKY%6_E?5x*Qq`FcMZHrt-5K}Yh1#XW7m1; zR1y*|e_QZ3ANe`tLTu*E>U`TL`|4%^)SpJOQL?T%prf&|W?cTLkE4)nu!>2{;9=#t zzw(PGy}Di+c|;xV!YKgMvayXV<~m-p7T_+Mxwl-?Ti0Z@0LUckl8?R}8&rLizyU(~{ zwND+Jfbc8p!gLys!d1yo;UOP;Go$T%;ri*qrSzbaSdxydeQ*~~r}yvg$x|4s79X4^ z2GMR$asj)*;1{rk0-NA4qBXf&>t)f|M8=uwqOE!zN6Gs6f_3wiSwzvTE(cwwgi~nq zMb?bam;}Gptf8(!5~qe~e(p*w2=hy>4XS(&h1w4>BZelo2x?l*mP%KjrHwcZC6bw$ zT4RJv&jQ47&$jx4$YiVo5ZFoe;_7;O*qnR2(k-Yo;UjH|{o$8?C8}2Koo) zkL|1Z^f3+%aFe-}J$tr-W99cHRo}XaR7Pp$`Jg|1-T z3()1UM+dXXlV?|A9K=&fyatQ?X&S__a6Ofb`X&SuE)LRkt#_Ny)4aPpg$w72gT!%@ zkG$S8$X#b-+H8+-M;ltk_)@SK^miTKIBq%+54xXGJ-E-b8Y+fLX$`h9tlO#w_UVjB z@BwMkD|>=9Y#rEUKx*9&2dil(lZKhKmd16GvIl{#)A}mv;0`sZ+G+`Y?B9d8^53wcX6n;9Ia ziG-6VgNSOeh1x%3BL*kaJCPwEC^@bEQd!BG^5<-a7O_#3NIGx+k@wuc!s`Vho@AId z{+`Ax{s}AZTkiqgoO_Y&NPTz=iV5m4`%`!P5#b<`GM%0tCxrrU%QjEPEcJ$)?eEm? ziDph{nY61xYyzWbG`212W9hD)n>1K?TUv% z@*k+@=R(u%b6eKC_q7M!0RGypfUc0y9|aJJ;R~knyoEp0!6K$Sry7$%jMqaBu+>g0 z3wKQyc{5IWzUl5~z28XQZ+*V@jbztq7#>N1@@0BF@?zQX=@SB*Bc@phUqZVxb~FIS zziiBQ-aG=AoO#XkS)xV5nC40$C12z2HHP)pB?6bn4Mfa9#6OvO%b)Wx{^e!;x?p0( zx8|HIvMA1D=vlfQxm>lnsd#x;FG^M3y2Vli%tSTKya>sv>B3~!v7SjZt_S`Pa_lnJA?YIiNk=S?#A)8V@6wHT>&9k>|Kg) zZd|bLn8^vaB~Ss{h^U19vp$^L-#K-ge2%q7U%QW3JcL=Mrp^07?+1K}{#LlvpFQEr z|1hjmO7@EkcTCRNyQ*k%D~SFG_1-ebp~LXK^l7_X2gO}l1S%Bai1Iv^ls&EG`-c~= zTg(R+YTCGJ+8MmYsybW9S~I>={Nk(WdS5qh`#4{aNZe~sw7YsbT2FXU3qi@t5EUdG zyFpZdy9G=u;d6T5bkb)iWeD*O&Ann^E*twivz?}=Me^x?5}@yQD11YwJNo<|kQ3J) zE{mJH-1jaq9!jB?AN@sC-5<5BZn@fL;(&qzn>!B8Y*MHnXxPAOcxLIm3 zr1$ZvcyzzYN?QNVnT3as4va1Xwdu2DS}b zh<`+)EG7tl$`gjs!`HLnz_ae^!@Z^)5bzl2vvn&RPkhDAn`MSQjS? z@rT<&Dk`1T+5+_n%dA%t*Vi8Z-?UB=1!W}g7tODgQ{Y?5x&jr$j~bIbrWrwO7bhU} zq}sBXHvpoDqY3$6FNdq2C0mpnxI zIt+AjN?eLgf8NdakIJ1mU2W}H$Q0{=`H8V?33SDkJNWl1iz9t#T&x;vk7^USN{^{B{;b-)LwY2nej z()b=N+>{cXuRJ{T{=`TaDOFAei~6kEF6nM>&ayu;b^y)mY5u_owC&SAr#(;cEy|>0 zlPULsD*|_|>qz$~L*KT(YxlY}plRsPT8y)N`-nDju3@Wj(P+u6)Ucsu8tr#L-_6tR zPQ`X4OMpo@&_U-HmmLJtI|E5h^+c`^=w060*)jM%M9&__vS3aI(<+E^QCb!NhRB&z&Z^~-#QMHiGJy<+< z+xqaMZhOz8)nIyjo)FeQY^Qd|oC1o(q_dNnLm99al#cgK9}mFnONFB;N)Z-ZH}(Wq zX7jPl*g5p%7hna7SyT<}0Hr^NS_plp?-~fxha4Ro9p(d9*oO)INjUszIs12g5(z{Q z^MtQoi1M_7h22tE)OuPK4PWvLRC0mV)=}2Ya@()ZSU9ik(~v!OPKxJEB9*>urr!IS zH*SMXd}G!4y`S=>rlsG5w>@CCEx^9$(G2>aVrR}!)b9uQCdQbl?q`I6r6Y!idRs(-^y`uUK=TX;zd?T82> z>tm3PCjo*-zkzQw!GW6ofnf5--)uY29ol(PwFuls<4GGEV2i>EQyL)*oLKwv@X z_Mug<;d<(F)F4q0-6Yn^Yt6I(Q_*5nQXY!bO z9S-aBR~$Wiq15xMYrfn&TiE^mt<5#DRB?~#xt#lB2fKODmS|3N3~N5Slu-Cd08US1 zjch)~+uO%W((XAWvI5-%BKs?i7bFA(eBF^11*0zIujyz982b3Mws^j=v~~j{a}N-z zW-dOr?iK2CMatUo6`VCj5fQ#0?UYN3kCKfzn@bRP@~8DYH_=zuxp`f_V%$PTVIk-G zd0p5L`0gCvJK?E0lkr`bkbT_XyX_5j@W*2US=4nzbPsOQ9dMqek0z6fqbM%j)+S)w zk)w;0f9e3IR357fYU^%Ud+Qb~&cLAV1s)?7y0@sZWhk3mE_Gq?;azWvPg#?z0OI+H zs6#5$HqI9&0abH0c8z|yih^;=J)Mfr$;Ta>Q;cffvBWn*%vhn}%>M4NGj9iUe~1geYJ_Kb!Tdlv!#|Uo7Q;_&@A-5)`6PC$ zl{~~>CfZ?Z&>>;r1DW9PiNbLH+C+wK3E(So<{BwLdis~yFaZb?4eH6110<|@5FxQY zxcF8*$-p?b=J>lMto36BBx%>d3U~e#HhgAd#)Hy2ZM+au6t|b*kAo?V>Ebe8QSu{e z=WDi&pMs&~>SpR{3jN$zI=*7Rw)Pil=In^?llZax{iiHTWG^L zu|s#di=x2D>PSAZ)z2Zna0anC&rL0Ib4SSScSSava`RO+|#A93}UcNcNn3K=a&xp zrW^~ZmcD+Mx`ba!ux^S=XMRk(1J=h4PvUUN9yjxQwfe`d6by(SZL5xL{M`hcZ+4b9^G63^;SF zlc>~Nm>!PprOKBC-A_aczoWC_xh3n$o2ZgyE7{-)-R*fez6!Fx%{k=mU5~hzYxLU& zYW>X!@hnZ!?fzFgm0(PR7s0k}1VJnge!K8~%+4^#A&Q!&Cfib|T_d$WoD=adpplVTxxj5l(ej(gZg%zW&@I|~v;Ir20br>@YsSQHs_}F5uWqrd zsHE-24=*B2BK z(Q4`F5#r$ysQK7fi^tM!s?WUoX+^CGUoA1oz!h!=|D29vJX2CpCdn~zcnj|rO4So zI;!O6TgYpN#vW9y8iN7}>hoYp-g-?r^1sYu%UzlG)xrBzXZ2aTC9s}U${=mf-%*x6 zdlEBqm7`kxE?1>XMOm_z;l$A^30Cgok?Hh%EAgTel{xeIt$6?Q;s-MBfX({Ce(bG; z+y_1D&cZjq*Q4G?v}U<53iR@@)EHf6c!T35W)zsd@1u8-Cn;F+5DS0t zRM7E5(ru8#UH(V>$E79#6ZU~R{68`94x~?T+jG+XrSKQ=m>;*nZ$%sm{#}2XpN*8P zUY7zsb5OXd`OeOnZV!H3cj7)U&1-7Wu7)hW)RKk`JC-^=#3u5fif z1gwxwim8y-@yTV#g!?7rLQgs8=&H)UWzsS4D|aOM73sy}HBA1vYpTIY8Ji}x{cp$R z9j~pnbm3&8bbKEmt9tp?(x4&t&pV~`DJJAfK$ho|x&sg1MypDA_AvJ%I=tEu@%sZo zZq3)}cC(obVHp3Rz7^DB;b-BG00S#eJ#~X?(VhFzA`(A5WdVye#cgTj_s>!aaK=Bf znxct>+=8u}5O6SUeCm|zVJey>oMK!sAGdOq>S*JI4=}%j!U;z96yJ#Xe@Fg^`GK8L zV`5%e5ViTw#Dub3t-@VUVQORi9pg{L@_~F?8@EwJt{B#c=M=uerVk1Gg3zY@c$1-D zPI2?p?sf7cNl$F7OWCn)-M|K_g+~6t-B8`$S1}~C%BYocqkh{8f%WJ0Abx8}0itJPZiEQdK>2FEoX&DMCTHPE5E_uiY`LSH7IvykaVs?6?-Ynfr49 zV8*827Yx6=%sgl9Fn8%LLlB&e@)1`N-~b*Pwem{Dt&rB<++3NuF@wE z&Czn{RfyF|v-Q^^)}N3vaw`d?s^6`x+7O^`Y;=00&%4CV1ZfZ1m2`!Rjg*A&-EEzK zkJF!X}Fl>fj;%a1|c06N#DwxC*L3o>{6f=}A@28M;QURJ@q3HI?Bitj$@^AL0QGgF4eL@v~m#K+wl_0%a=U*Cf2%O@;-k1w=LGe zn^exkCe%2V1<9G9et(gn{m3Vm$>xZ;!TSN`j{t$fHmK?pae<$o)EIaEoEm z)uY_X5`VRAdTp>YO0Pq#zeeOlq@fGCX37gj@ljl(cS}4yv!U)aEhh$^oY4yOgg3En z*05U=Scu9R6SD%WgtO0#DpYNlR4Nvmg1%ut?QY1XP_S-3a5)9QDxR?bGF4=bd+}2$ z%xqNMS6bU{`gysS>k@`#6(xkyJF7mr$lhzZm!pgtSE{=Xq4i~i2>*P$BBmkHSh~D| z?P+j?{5@=V@P8UX2l_I59DPmnvNtmPsvvws+8n$a0{8P8e{Gm+;<*}GiXm^#7-wye z$2$IG68Y_fgX>~`T`1iGz(|GZ*~THIR2{O2AEP;dapLI@pFDFpA*Rj1^+P5ZV6`F2 z(a){|^CDEIo@!q;l*)djE$9(hzTVW!PWDDHe!2UKTmPUwLJcbKvWG&$Crqm2@`c+$ zRDMSeYgIdK-X66Z;dPq%&p^YZSnG??Z+<342Fa#-{ZI8cw+qn~T0?`uiILFbe^K1U z-1uN!7Y!&cst-j-6f6>09W1`)q=Y4O^gq^SodzKO3b|{+QDm`7i#Na9ehHg^aI)hb z9%?IH(k1sX0u5oi(JAV1Nil5R+-Tzc?<_=?jJp1p(*j&u2`~RtWT(TyU*c9Jn`M{k z?@)tCb)3ea(4X@+T2B84a5ypsG7%_F@zxo0%gNV#>;FS@*rMxfZZ&HU&jd02SYJ-% zOjFD^wlE{Mu|wQph7&?~Lz{i?h5jz%$2oqAH5=wj7JrESznv$p1RRXrt%8ytx2EUE zir`X>d`-sx?(&C4l92*3LF?O}tFfymJS~nWUMD|h8AOhw;6Qd-Q2TCDE7zpHU{tX6AK3{k*MA zWC19?8x{Vdq~44-W88EgmO!TcIotW#_hdzWzgEdwPRY`7 zq7F~1>QO~1N>y&4#u>IYMim0L)`$+mTis73(?_hJK`}bc(lQHVx}`q2KsH0v;DLn7 z!hG-FzyXDs*{IOfj-7uw-%cJgtQY(4SM~q&hEeD3Z|u#~H+5U(y2VFdIRpj3-e`w{ zxydqi2$jUs4Qr5xl_bgYiQDB@ar4kJ^bdW!_yvLRz*7vzMqfU#zvFsuqCh_*R z|H{Juq8uAJW+>Z9iaC*zY?x{PO8Ew|8$}4p6ERAe^-%LG0q~X5jk{73zp49~b?g`? zp;s{rOx(E4%uPEFFn+puMe0F$;YsK=SdBhSb+ClQ@knXFFhR^^8}@-T&bx7t?kW4i z;lIsCwt#iP&k76iQSeb5uyq8s;3G8%!r)zD;!d9`*yQ`LwP%QM1a%Z<1T{M55Ym%D z+EE7q4b`f##WO>Cp5TkA>l!tS)1v#f7 zY2Q3o=}MtFU>%HndH;qP_hV|t>22$k$N4cttu(Fosen7!7Ji-jxVq8p7@kS+ zmWXhIzF2k*_lbjyMD;+}2heGSLh|wqu}-VnS7(G+r}0r9)e$0rvNFHa?!y5-LncT! znZ^FANW1BcgAe_XO3jzJ&o!<&Vcv9(!iIdY$vJ)&(Y=##9mcHR{~Qc=$B!)_&x2$X zH)F!Fz0^_-IVFXMQ<7GZ2~>vd9~=YU6(#UIOc8HuhZsyO$2E?*?oBoCcS|C?4z-O zs=J{7!6r!yza&mRo@W9JNJi7!d6w`{mFqSv57qvn% zkl{q6i3jgWZ$}08iML}sIjO%(_tF;7_m_$KkM%)tj;qT@tGf{(sN0}%!a#py3ZOVt zIV=eNO;wTv_sOl{FqmM8dzZ<8;Q0fV6`kPzuCDnMVvk7aDyijaYV2yp2Xr=rT%g!k z*f Date: Wed, 16 Oct 2024 13:54:28 -0500 Subject: [PATCH 05/11] Removes the divs --- python/README.md | 89 ----------------------------------------------- python/README.qmd | 6 ++++ 2 files changed, 6 insertions(+), 89 deletions(-) diff --git a/python/README.md b/python/README.md index 8f3604c..77a1c10 100644 --- a/python/README.md +++ b/python/README.md @@ -99,21 +99,12 @@ reviews = data.reviews reviews ``` -

### Sentiment @@ -125,22 +116,12 @@ text. reviews.llm.sentiment("review") ``` -

- | review | sentiment | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "positive" | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "negative" | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "neutral" | -
- ### Summarize There may be a need to reduce the number of words in a given text. @@ -152,22 +133,12 @@ an argument to control the maximum number of words to output reviews.llm.summarize("review", 5) ``` -
- | review | summary | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "great tv with good features" | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "laptop purchase was a mistake" | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "feeling uncertain about new purchase" | -
- ### Classify Use the LLM to categorize the text into one of the options you provide: @@ -176,22 +147,12 @@ Use the LLM to categorize the text into one of the options you provide: reviews.llm.classify("review", ["computer", "appliance"]) ``` -
- | review | classify | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "appliance" | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "computer" | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "appliance" | -
- ### Extract One of the most interesting use cases Using natural language, we can @@ -204,22 +165,12 @@ We do this by simply saying “product”. The LLM understands what we reviews.llm.extract("review", "product") ``` -
- | review | extract | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "tv" | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "laptop" | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "washing machine" | -
- ### Classify Use the LLM to categorize the text into one of the options you provide: @@ -228,22 +179,12 @@ Use the LLM to categorize the text into one of the options you provide: reviews.llm.classify("review", ["computer", "appliance"]) ``` -
- | review | classify | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "appliance" | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "computer" | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "appliance" | -
- ### Verify This functions allows you to check and see if a statement is true, based @@ -254,22 +195,12 @@ for “no”. This can be customized. reviews.llm.verify("review", "is the customer happy with the purchase") ``` -
- | review | verify | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | 1 | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | 0 | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | 0 | -
- ### Translate As the title implies, this function will translate the text into a @@ -281,22 +212,12 @@ to be defined. The translation accuracy will depend on the LLM reviews.llm.translate("review", "spanish") ``` -
- | review | translation | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "Esta ha sido la mejor televisión que he utilizado hasta ahora. Gran pantalla y sonido." | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "Me arrepiento de comprar este portátil. Es demasiado lento y la tecla es demasiado ruidosa." | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "No estoy seguro de cómo sentirme con mi nueva lavadora. Un color maravilloso, pero muy difícil de en… | -
- ### Custom prompt It is possible to pass your own prompt to the LLM, and have `mall` run @@ -313,22 +234,12 @@ my_prompt = ( reviews.llm.custom("review", prompt = my_prompt) ``` -
- | review | custom | |----|----| | "This has been the best TV I've ever used. Great screen, and sound." | "Yes" | | "I regret buying this laptop. It is too slow and the keyboard is too noisy" | "No" | | "Not sure how to feel about my new washing machine. Great color, but hard to figure" | "No" | -
- ## Model selection and settings You can set the model and its options to use when calling the LLM. In diff --git a/python/README.qmd b/python/README.qmd index 8a7cb4c..2b89986 100644 --- a/python/README.qmd +++ b/python/README.qmd @@ -90,10 +90,16 @@ We will start with loading a very small data set contained in `mall`. It has ```{python} #| include: false +#| import polars as pl +from polars.dataframe._html import HTMLFormatter + pl.Config(fmt_str_lengths=100) pl.Config.set_tbl_hide_dataframe_shape(True) pl.Config.set_tbl_hide_column_data_types(True) + +html_formatter = get_ipython().display_formatter.formatters['text/html'] +html_formatter.for_type(pl.DataFrame, lambda df: "\n".join(HTMLFormatter(df).render())) ``` ```{python} From 5f2561cfd06186f66d4fb8ccc57b60fc95503fc3 Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Wed, 16 Oct 2024 14:41:23 -0500 Subject: [PATCH 06/11] Updates readmes --- README.md | 9 ++++----- r/README.md | 22 ++++++++++++++++++++++ r/man/figures/logo.png | Bin 0 -> 29279 bytes 3 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 r/README.md create mode 100644 r/man/figures/logo.png diff --git a/README.md b/README.md index 6f14838..44303ec 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ -[![R-CMD-check](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml) -[![Codecov test -coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main) -[![Lifecycle: -experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) +[![R check](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml) +[![Python tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml) +[![R package coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main) +[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) diff --git a/r/README.md b/r/README.md new file mode 100644 index 0000000..ec6fc40 --- /dev/null +++ b/r/README.md @@ -0,0 +1,22 @@ +# mall + + + +[![R-CMD-check](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/R-CMD-check.yaml) +[![Codecov test +coverage](https://codecov.io/gh/mlverse/mall/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/mall?branch=main) +[![Lifecycle: +experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) + + + + + +Run multiple LLM predictions against a data frame. The predictions are +processed row-wise over a specified column. It works using a +pre-determined one-shot prompt, along with the current row’s content. +`mall` is now available in both R and Python. + +To find out how to install and use, or just to learn more about it, please +visit the official website: https://edgararuiz.github.io/mall/ + diff --git a/r/man/figures/logo.png b/r/man/figures/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ebeea89fc91c9f9a97a8af0ed81bbc569b05ca6f GIT binary patch literal 29279 zcmV*bKvchpP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 Date: Wed, 16 Oct 2024 14:42:47 -0500 Subject: [PATCH 07/11] Updates link --- README.md | 2 +- r/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44303ec..8602420 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,5 @@ pre-determined one-shot prompt, along with the current row’s content. `mall` is now available in both R and Python. To find out how to install and use, or just to learn more about it, please -visit the official website: https://edgararuiz.github.io/mall/ +visit the official website: https://mlverse.github.io/mall/ diff --git a/r/README.md b/r/README.md index ec6fc40..190efee 100644 --- a/r/README.md +++ b/r/README.md @@ -18,5 +18,5 @@ pre-determined one-shot prompt, along with the current row’s content. `mall` is now available in both R and Python. To find out how to install and use, or just to learn more about it, please -visit the official website: https://edgararuiz.github.io/mall/ +visit the official website: https://mlverse.github.io/mall/ From e844a22beb07edd2b9d63b976c250d7dd53937ad Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Thu, 17 Oct 2024 09:49:22 -0500 Subject: [PATCH 08/11] Figures how to name project differently than module --- python/pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index edcc881..07e9a05 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,9 @@ +[tool.hatch.build.targets.wheel] +packages = ["mall"] + [project] -name = "mall" -version = "0.1.9000" +name = "mlverse-mall" +version = "0.1.0" description = "Run multiple 'Large Language Model' predictions against a table. The predictions run row-wise over a specified column." readme = "README.md" authors = [ From c0c68383c7accab05a00f34bf778e48260b6f83c Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Thu, 17 Oct 2024 12:34:28 -0500 Subject: [PATCH 09/11] Switches to published png for logo --- python/README.qmd | 2 +- python/logo.png | Bin 29279 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 python/logo.png diff --git a/python/README.qmd b/python/README.qmd index 2b89986..3bebf77 100644 --- a/python/README.qmd +++ b/python/README.qmd @@ -4,7 +4,7 @@ execute: eval: true --- - + [![Python tests](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml/badge.svg)](https://github.com/mlverse/mall/actions/workflows/python-tests.yaml) diff --git a/python/logo.png b/python/logo.png deleted file mode 100644 index ebeea89fc91c9f9a97a8af0ed81bbc569b05ca6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29279 zcmV*bKvchpP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x00(qQO+^Rj2^SCoIfn>$;s5|~07*naRCwC${b{r%*;VHW z{`Rrqj&FYceD;`UDHNJO5<(P%nuLO;$V?<9nUGAPdaYUuMdpWEy}By_MRsQmy;dh# zy}G&)Vul0-QI0KE0DY74yN^6f z3{+rRl=lNat?0LSzTaprEU<9$1k;Q2B>jl>iRt~#C`Bz zy#ef-l{Wx=6Y}Us9|j14cOd+fs(!zSS**`YvvA@Bvquk8Yt{&SgQCPy%Ffyr>*p`9 zyS7Fe#|iKl!p8;vgM;-Cy`imdO5OnU4alR9{0xAnDz}TsKSTA0Rpj8}?9%cUc zF`6??g1|#eR>hLnv~^0q6R~w=mG$!%=xlG}tlbsiKZE>|!e<0JANbi{c%>inH7RcZ z`b9{_UMMIF2>*>LKMB0k4?}G#42Y&7ijs0Te4M1O~K;agGtu)=p4&zo+}&9QLu1hY$r zsMIQWo@BMEiin^HsA4?H+JbGxAXo&FZg2l z;2SjGb;%omzJ`3_qYopBr-nC)^5csBkcb?sHk-_!IL`dBqtu%Ew2n#2U)0myY_dQI3WR=Xc5U`PB z_n8(u&vvkprLqvR@Zb#fW3@4DUAzRrT1ThVWn+1Tjq?`@nr|KCPY^y1?lF)Eln?&w z|N2T-(W_G40Q3ZT^pSr7=nU}Pz=u`!y`Jw^rxzDlICYZg#d(4-7yx;wl`}vmt(5I2 zc4?pMqG|S+3u8F83L)URA}b7-sICV)ni%{F*ns4w7GoK?8_-30rGhtev~S?&>OO9LEA*Lgbf$ zKTb*4JUl-52Guu^HvkQM;*p232ID*KMEMa#e^^9r4eNC?fBYB=$B$8KHZWd^xzNGK zK(lt2`)q%GmuNFaa#}-GvjL(pgYV)AQ@1vle{h=0bdZ}Lqkt|TZk^I?_t;p@X}+y3 zoVB|M|3#2r1pTxc*A?sWMtsOOIG`VWlG-hY; zeQ#)5zzCob(A3dB+hhC57HKCP0dr8V5Kh(UuJ54MWxwSE!rIXai*K2wcBry1pmX(2 znp#@hZPqSaX7l1j`UTC0{6503f<6wU-{@}iZ%9Bt`pCnmrige=koN&Ut*YN{Ja4+W zFwdcrCz(F9fFA}KzC?zm1q=ag!1gV>&$iipu7itpz&EQZ)wwB7eCq==j?J_3*mJCY z^#xpFM*v@)51D_<49yeuY_JG>09`O5iW2uNTkBh_og2`6R^SPQ-vAy3xd`Yts$<4` zr3>(KOU@_zBJvQ(f3K?lXAyZveP*U|`1ad5dgtv-&ClZb-T<7EkKD={x)}UzeSMeK z=`NaTu6-4NspE^B`mVPSO~qVUKf}`9w^46S(_P)h#+eou5z?New;Ch9P?-x5Z>VDg z85wkn2tnY{n5xrUn5WWc;;b`Czkk>{_dXHc1`?rM{Gso7SM+i0Y4OM#FjY$1-z7aC(ijsqx%G|*(Up`-pMHYgW~-#B!K()`brJbf;Nwp93j)yxzk%40*FB&g{piDJ_LQ^0_o93l z^xa+%RA!bAv3T+%&G}i3=N0%Ja>Ek5 zp107Loni6hNv4+$5rn}|^W|*ABtU0_Kc#)9%kI-{((c&cuTkaLH{VPB?s+a;cmj=f zS(u+C3BQYE){b)Jv(K^h>_yb-$eXAgsc`6_S?Wisd%TKSu|`c1 z0Cmvo#cZx@uzEI+=8K|OM82%>%b@>ZYARe=U5WXz|LOIsv0s;fe)OY11B$_zyFvfC zD*r@~TdK{*z+D~IYLu0u$W;Js1lzZ?p6$?jzJn%O0+OS)FwKegy%{}ZxOn#KgeGNv zZU*1?hZUW3%dK{YwT&I77f*2F$USU+@jNSEejb~QHjv6}$l^mYOx@ZT@ZK4S%iFoP zks-)4Z)2aeODn9MJzvm#9fZFS_!#gvK=<{^hP`Ht1^zd`E6xe24M9#`MAhi>FS~Tv)(( z1{n*YxE7$J^_ZQlbxo)O{KP<|KRzks#8UV#0Y2lS(lJPbmB;~|8fP}Lt4W0vaEQ?hXK z1ha<^q!f#uqOHhBbGZ@JITlWo$SbZBCz;)Onk-+Cvr51n9nf|MfzKPt-a>MR9~}ze)N%tv+x0J}a>`c+G)B_U?PB-Z{(p zbH(6qhA<3Ayoc8Wbm1&f$M#N(&7CfbM{j4bd4ko?J7lXu&Ss^m_L{DH3qXoiEo&Y3PQaU%n@`r-`7 z-}eA+9xlA_I2GSAH#?2*d3(Y~uLsaYF;HlCx~y$%)0jQN@uT;!{gq`_zVsYvG)fIt zW&#!;nq}tpCdQNN($E9Y=YF3kO4(lDVfE}qHZCm_b-Rk{IT8N7!f&EH2iVslfBl*Q z^zZ%r!#JTP@GjuPihhsr%xq(Bj>VHFnOa=Hcx7hcIsu)3rn@P-Pqpb?>Su$?93YA^ zb83lW?|Xoj+2-=Ordxrtm67#NM?4J=_Q2oYL#YtY2Pd_3Q<9R#u8hyI%(R6@fo;&aH`ve(>jC zJHUQT0Q%8<)`&CQC-9@H`XLcHQEfJ5;ly!fj~v0TREqJ?@Zqi(&?xbG!tN6-;*Df* zzN%t8!~DG`IsERsSlzwA=9M$d%`|D&YZE<)Hw5Usix7c0Nm$?5B1%0@-1=txZNtS+ zJx*tJd&CJNzA$@tlS6NrC1?gCG~@LEy0}!DI@-HE)-JBFdiFf+%}t!OZQzp#9|Jxq zNarhjg@5z2fBi~B;hOTQ26Q&N+6n|mfPbXQkAprKR;&K(kt58XI7X$>1oE)VJU(LZ zaMu8|6obD@eOgbqNp{l`XcQ9$9DduK%)aF~7cW0UZ+nG>eDGIP-hlv`VSqn?b(XEI z9d_C=M^4?#l+LsK=_lDayE0%g2A;)}b(Y>fM{S|KQmJx--+sftMES0_v_P#7FjrXg8Izd zAP{++63afzPdR+CQrf49qjRQ5`?(IbKQ{QAu5tW5_Y>Ya#kn(&qe+X!xtYwf$nd@g z3h3fGc|+T6wOQY4F}HLpOEb5y`uWqWKYkWx$87thDobylqj{p1@1Ha%+#k^8YxR2x z8!KB}Idh)vQKb%LJt9Q;3lf?fy~&x*QK3MG>oOo2cL5|wot)@1}PsRkR*=XtqxbtF0*#-d_nVV3t2QD!e>#UR}-jT zaX^3KU;m5Dnc>_b@O{8fs_HxZFs#lVUSj_EaVpKJK~aY^6&Yt7Id;CdOS~GtxTg_D z>}Byh7VbaAp>MjKmF@Fvt)63Ux=y2B-#@_rWdgJSNt&{;xlJd6q;Jc0Srl%I>nLlxy#{4`O1G&4mG<#_PXxDe_Jhnr! zkqoG<7bDrk)qtaKzl-Syk8%FuQ~BU;hHABvSJEpC==`Cbb8Kzzvfb)&=)^tDm_?R9 z{S@1$FXt}B(oL@C+4KL8y?c?vZ<`-z=(0DnK#4k~(~enNUgydS=L(uH75ED9%ZU7u z2P-M?!B^0Y{)zzl(T{!vz(=?r_~)wpkcgb9O-;%C@#9P{En$Kndq*YrTbVN+0J`Vc z`QnWMT2-k`*E#kr_Y<6GaQ4jCA?dI%KSSXA15@M`*U;rwtgzc|v%a~@^ukG&W^ZHd z@6WLI_!%^@lkKak)d9+}Z_WWN`vbbLwbRtHyWM5w{0ggQ&(Yr6!lh{&kxzma(R{5} zY9{*23g|~4c^LITEXRN!%9FReIjmK^xg$rIJ$jU&))*=n&1tr)0@||k#a)uk7Z2$A zp;=D6?*XEzl=G**PQ`O$gFjw%fR?O@)9dwEUEd<8&vX3P{j{E3W%={Zkaqho;_YXJ zI`++r9De&X06jAE0pciSb8VL^XD+dR{sR4W3sqea;Sb3)_E%BTmpka`Wdn3Rmv08) z{R%&Y>ihg4s7);{Fn{6%)v0N8pa-M`rt1LoW4pLE9C^oG)DKM;)B3Ld;6|t{HreQ{ zuzc=mW*Q;WQ&Xe!2VaGNF4s>S$E>Yyky7ROsRyvjG3#GHi%rsNzgktIoepuk$FXl- z;PBfQUKF4OaH{mW3G0_Px%|R;wl7~M?)5D2oQV87@Ck5dLGS^J;%{g_cJ$hG&}wpqE5N^ zsmEFQ(rJ!<(;`RSF81a6H1x+sseN^7^r%J~(poH<8pZ4H~IkqDndO-A3;RKIlT+yhgJ2TiO6l0M%~OEJ;wCm!+1euU|tN^B9W0DQu=Si zfdXKJXg6Z_i!HS4=-rZ%Hr+@-&Yts1`kk0~rV;ukZe|Twt{gMFs5B|-+ zB#GUk2;UF-Vc_jSB@AblmY6+yjG*4g)6B9jc=9@>w093sM1+1TqV>gH=tJ$nI{u=+ zulKNgO%Wjs0zA)L7nsZU9l6fve`EWvAIxRoitODt5zM9ig%hUT?%>po^lBsJ&#!N- zWA|%4l5;i(^!(&HyB3&BKjm%#`u&uR%Udj;xxnVdOGKTnf}R8U1n}$5>9d|O_5(ln zZx0-M2MXxlf8=4hQPK!}?^_Xm8s)n@-txbLmDm5Ujs}=O(^a0;9Kjzf^V~}IjW>e;l9;H6Ni17pB#3I;h zyfh{NZ~~}v4V0!SNtz-Sk~BdQliRPQm%ZoOlYlmSY2~he?~9zIIfo}*7bwTnNErxa z6|=9&aTTCnwcbkSCrL_bv${)?gg8k@uTc&cDE}uTw;2qgM{b|rr&{+pcH1Euvkk7C zn`iCZ1;4ws^)~11-6G~6%uO|ZDNd9B0<`~^|I&ajM0l5+90tF)i*bNKD^ z9DeKEgmO(Xw{-8bTt@?oBFpxAo7MBn*-BuY4kD}?G5_^SW)}4QPW7jq)AwLZy*e{PxOfPu)Ui%;^Fk(8?0zkK(*Dvb%Ih)*bSI|$WCw|b zsRtSa^SQ~f_cqrA*1pjDq-E$F#-aNy?{WHaBS$?=O%Tcswk_hooANNB+2i^(qo-V7MNB|fv8CgOjh&nuRuj)H zm&q#!4(%f;LbSIjl_a}HRTOXRr|~0gUN*?Qq){uy^({fuC#VNG5H+(y_bHeHsmes} zddXVBm-0Itc9|l=v3G32l8#8?Kq^D+=Bbjc7P$A}6(Jqdvg5N~%9_bnW%4V{o-Y-_sN(N)jLo&KL9aT9 zQ8GXS_~;(n8#0|Sa?Cz&)#b+-6hHr6ouV#V&`6A6H3O?B;7=g46-ZRFaMcNov8bao z&%Nf4AES@}6$3xCjka8Vfs3inm*WaYJq9M&n;S|Y1_C9bojK8dv$&tviM zG>eC3*gSEV%cn1}e&NDw((C;b;9{HY#|0VAi92XO=f$q6YEYk86r2p}WzeHPv7RnC&LqveC<$(FmX+ z4h&b0JG!bY?L-{k?a@3L5C+46OAqCYDiLrhP>##!=(GUq3d)lyR8ig>TBgO8ZkBoQ zE-IXkmAZku0;MS>&=)qoK$<8|$1v-`eHDggK*?Ef=GR&PE^EjxYw+ zPYK-7q_!ThaC&p36^bhDT8RJVCeAm^UEJk?#Tmlc3Yyoq0WQYE7rIdMga>9kW{eOg zmN-`0QOfhFW7B5NAZs1qsMV{Sy6q5ii_<)9;L_920yG3OVtKwqfX*M#7*q`8AXYNF zta!=B7=pm3TB%T}gap2iF@{hBa6;e*G%B@`fke{vA#?;@7U>rFKJ~CV$!x^)4W7wn zW>kWJMzuz>Q733*J%kYu6}+B@IH6Xl&?pqP(QNvV1~h7Qv@!&KAlb>jcvMYgkL8{i zAIbmusZTAeQEOHQ$9Mwuo+2j2^Rn_Zj3Y==JkOA*AblYpVC0y|LwG%5X0}GHQRQaj zKzQi#3o9xi^?bUO8X8teg8)AVbe#I&AT}OtzdAx~*@{PZN=Yi&6sx2X(4DSNyjScS zY`#=PGF2y?o1#Bcqwl5JO9XnF*|RFp^#WR6Nz>5;4nb&%M^Ks*SOB$+ z;;>j?SNt)GQ3k;8Vo_2O3_JK>44rz7?(`7QaW${^(5E+6?*jBg59bL^gkHVMju#RU z495NjXsN}KlB%$yem0{%f973LlF{M*AnZm<+czTz3h43+xe_+m3aS_pL=BQ=rS2)H zvpky!=hLy<`yTF&kiKtl&hgxQmGd*<-h1T1JiXA6qI~&SgQo%yBE|L0-~~9K&k#6O=vYTj;7_(-GzvA-)rmr6l{E{0w82P5qO$ZII1A5qV0mIBr_ghXv+!gK z&UPonTjVd+0=sGUH&G(%_|3C@Jjpb$bAoeK+NF@~ zNw4FEc;qsKZu0y+QgqdY9anx^P8~M2q^TuwZj?6#)G8V)i7%v{5IKw0BHic6lulHH zxbI;RdRS~sp0vDlKOk|6Q;3b=_F=!QLShvI#GXt9MVcSuM2Nh-v|=H#&F*Q85P1{0 zTp)HzYL(aszPJA?v_O(7C`5rwq~%hDG|%u9`7#moNM5egAx4Ok!Y=gjr@}!4ijsu1 z+a(MG{N`RoVpXyI2%B1B=P>w)U%Zo?DG`Hn1|tSxG@UTTfK^A_GY2X+4rtkbrvgWl zE)Tj6^=gGM3^B%#rYT>FQoho&oRpM1{Q)bQWuo7XNtaX3%vQKC6R?zaIcj@@srJR6 z5lF@3S*fvu$K594SZD~naD+9996Z*8mW6v8a42BNa+JFEtrR}rRRRNVoifzT=rbCG zwM6-R*HQD}ozosQGj6_Q@GNkBhUMf%kM*uw4&LO*B~Zt#OK9R#!Gb!vX#`$KFx6la z$JfWYb{8l*t@M1M>Koe3a)HUgLyC~9P*GSh%67p|mBayw4}rpI56+Kh=;4>tQ#>!5 zy7+aEk%3(CGLsPe86_6@swewu=zc}1`mk-_3&js(wqdUVHD6ix;PXy~{f>+tYf+fa z8v9ofymUgjP|S>CfiF6P=k}Hfpl_7woU-EypNR!jaSr^=Z6uFDZ(FF%&9f|qzuMT| z^G>Z(l6nKbQsvy7vML_Z9X7{ojFi&pMI5OI9H|->Du#Y+=_-V=QhDjH47`MZhRn&3 z5i{Vdr5`868sJp|Vjvnn!3T0u!zath*t*K~h0IOc&HpAIlbZ+aYm`;;Sy#JPIVPWn z!1gt2)ble3!sdSW%8!{?2V)N?dC$Ln9mj0JXlA96!h=`I%C(`xeWb-30z~jD6?#A~ zNk`8woP9xn2?9DmhvBIJ>Of;iq8MvataCUOoPrxY^d*xUr|n+Ev8q{2N1CQsaWCo` zFXu6@#cQc5I^k%!-s2_>BCn$W`wi%9HLzhK0n}en=`y;-7rRe61 zZD8mR7&~q-1}W{IbnMNFl4r4cg0c zX`L!Irz%|@17L*wI5S{y3I${HKbHghreBYaZj3VLd!M9C;gwzXH!3#_Xib#lY(&~~ zq&6i^VxlM_oeSVN*p{Qe65&+^8(30j>390rLp7-1=w06s$iVCyL&Y;xJ)h7s7|GUD zNmYrgrJq_-mzj5gF;skyis#|yjD}Suww5UWOI!xgJT~+V6@8!MaDKOiQ=2(J z1}S!LTm;yR9Dj1YM=kKM&XJ~;er(uC1)Q>Zy2XWOE%jQBYFHugJ;2bu(qrYg6U_(#Jd;HkW>eqfKq7=xpq4uB zU5lAc9I>_ZlZcj$*_f`2y0>kpvVPhi5r9 zH&3%xC-g&7n^M``rWV*&jM90fWk>)_V1%zzJ!ML57zVK4F{VV*m7^fX#q7gHALKI+ z;y0wc_iE25H%#x|AfS~Lha7JZ)|yC?;QD>iPKdYDhE9YxQ>8jLMXlMS5>`mlgxaQ$ zzp;}IcVEg{9Iv+hw2n-6K46Zqhwj5?uA6U)NEIe1>owXS`$Ov{JO zI>+99oH-K^B{BVekL`YsYDEzv%-wyQ<5Hnst5c~|@_2cVjkOnOoxYf_$nfGzbwhv_ zggEN)wJ&^zBz2_L5+@1$exFN?IXHBR^_6A5{PbmN)e2z{kXp-5r_1Kd5oF=WK~>*X zWe{UjEWMUX;ZP{HN6xt(2ZUZdgkVYLy@iFvie%avfgahJ6+{1ZV{+ zVaW8{bPnj0D2nNY9&14eD3!p^GT_&$13(*5DuH(+x4fzhyV$&ZI`obe&WCch>q<_A zvO}Tls4#F2hpFM^kYn><$7=FX5*9jP1M0}69tbRhTY9#Chs z8TH#-c>eF%^^`cZ^!t5wdog`;2xsc7UwNK$8wvG#Et}C4_=pG_XD{QdyWz%r!#1=r zhFY~s=&~3M&-3#95Lq~(1|iN&VQW>=z|Z115{EMf>ut}xU4!8SA$5IfVi&2X!|6N;X?1R+6Ys zuj`Rkg@e%1N@h|NPKKpMom10OZ1*F!`+Y=&n(uS8+2F+V6!Wzzq34lW$9BKZrFNSO zyDfI37^jLS8DD#HW{MM2Q_NH=1jZ0s$5y}3rQH@6S}nHw5#7`h*_6~}9=CBR8dAkG z)P0}1YK2>7X1HT+mSa;*rYjXHxdKR>qZLJ5YPETObBingNz@-thm`|w9 z#HrFv63%Y#@Rchotaf@Fs@Hhy(Zk#_J3}J~1~aiC18pacIk&ybW0$Y+{MI%*k?1qGpa5C6nY+Y->2^T+%-SPOr=8TdCb zjx6ylr%rIFe(m5DAuH2^N0vA-J;j%=tgzbd@X*o2+`BkW;vDtBCom=h$^q2hOCwp5 z_ob@GkKNs5uHq3VDZPHoPN&a*+B7W3ui64$_?DB$+380tHtJb#p;?fTZw%^631?oV5*RpK^*B`Zh$c?ic9yF#ZC_<8? z1d(8TOPFMr3<8fvHKf_d4E{8=^wSVOJ^1cs0OhO2m*n)AxlT|N@xwtvjuPQTCD?H$@t%uKbyt+TT< zE2HrTMugkvW?87!=p-qg2#p}%zQuV?&P?x9j)c>jTUlB9eXJ^tAmI3PlY15xSZLI* zS}zCc(R;}x)5X?0cDMT&Ux<<%(A|hs1Jnd`J26|aqu+;KCB^r%RB>@^Zz`nZ=E6sZ z<~e9p&AU$?`nsW0#?Ed&ok)_OjN|OCbaM85yJx&UD{P z+gNWN(EH39UbX>$)i(sGhcQYTrL?zGE*lSd8K+=%l7!tjrWGdxKv#@MD~fQrO+#ISCR_c8-8iP3T5LWm+K7<4 zEPLUZogJQD-{4&*j$JqF@@f9d(%BIvS%0rJm!B~N| z)nh02AjxvsrF~1ko#NNRmvzR98@R3=k+WHbCYR+b-uHOx@;{3~NUYu0%`JIshKFIM zQo);$M{K>@W24ujo2GdqaoP1WODzWAB`Ga0;rz}nZ(2IUO!dYYl2?QD96Z^}$b&3< zikGJX6MS`}!Cwccv~O|>RuP>GMSOTsw&PyHeHBYJ4DdXUGzr+Q1U#J@GGEurCt0x1 za3*v)_-9-)kCoyy1D#&OR4Qs9fG5V)}VLzhXvqUV#JlrHC)ti6T02{F(>! zj3>NjTB!SwrfKF}i4&ft$}ZmC_kTd+s{^y4rSz%5g0Vap#!NMQDxr|19&65Ltz}r_ z`qI+|}57dLOHU4=_3z>tMo3j422^4gW5!zk0Td#Web+`ch`5LYRkH%LH9mbAR) z`x7<&Yf%OX%^Ce0R?flS^X338*SYU?E=p!LbdzM_jFC(d%=T9sF2`a~|5y&5XnK8sBvMC?{uTZm<-uVR^N%9M@CM z@}D`_vcD>%%u;OhdR*LXu~6UZ!agxQ#dqFziZ5PX;oSBP?IcC>SdPQZI&VF4n0pr& z2wubd^N7gg4*I2j|2H2#I^x$J7S}=MDI{Aq!RC3_UT@h)3zH*pj#ixT)Y=BO&&}?M zv=b3d&djh_ud~|eveoaCI>%HPa=6i8rdqkKpsCkN?WtxUy`utVT5Tphpjx+B4-^5RX48wK;r-75;PYvGeEs1%;Uv_h;tTT+Icmd-dBxv7NbMYf$RtWiMBAd+rV zeYRpCE^^yBqL#(0@$%-jo4^-c3mAHx`(T)Mz&a&S$96yB3zsi5SF3W@{M-!(8aY)` z>j?az6XHfA(5~ls4_fng9X!zk<7IOSGf!dGa|h%T3&rI4G1;HFd86c}reH~B9)bpv z#EA4Ut^@rp8l{{F1MY8TGkSf`BZ*^nwwgTEjSkeYq9qUDq_?7!7X}@A0{2TOGf2!{ zn(MaTwX^FNEJrpQoCGelI{fw7^9XSJ-0X{v%Sf!{?Dh^D-7fbX%H80~9`EIT?)aii zMXbuz@{hZ#)WwZS=4+U%gnaugCuoFWmbWF2>Gu14wbfxeRVvK|8dEiDjRw_9g&+tp zM%Y=~y{X1^V}LFyz;7azIYeT__85%+_$vIbQoS! zTRT-Yx;>s;Tj%-BEfVYKrIvS}JdTKz(k={hcuc+y!)Gp@XTS27$2JED>}C+~&ZR@l z*Q#00mTrfQmf?kNkL^_Ot5Z~_W~nwBRI62jAjBBKuN2XWFJtB;DDG8M!ek#{;X!lC zNM4D9l0uut&eBd|w)%Z4V;t~K6lKZ3)eT(Bk#+1w+3~)Zf$Pqp>_!ohEn+URo}sihUi#CDjc!>Q6s)6Chjv&(K2@xt~FcQ4Fya(asCN`)X#P!2;6<5n-5 zRPgl1CYM`n`gu5MD~YKFKDW%w(2f&gm*wmb>u4o0TfP1s-g_tN(@#?pmnn{3nz9o` z?DV7IyHn_=DeXAU!Cy*Ab^s%^K`-ITaWSJWZ*=L3{Z>1AadKd^-F9EOuxZ)sW(C@f zloz%f&Dary7MnV@wj7;UIgqpxIZJQzjP*}%Y_ZjQikk1?8H45->Nool?Ig*xXNFoL zYx&ZZ6)x=VQuTd2k->G=dp&w-noWKs)4<&{gcDI)owRmAat08 zNu5Iz#X3i19jz$g%=Qj*)e4LC8ne|3RXQ|IWVX_gz>+QIW4 zgop^eq|K98zC<&CBuVM_`n0+c?RW;&Di^oU;6;{W3#Yifat{I5%1C;Bav<^0*1?%= z$C(YwhE)il6)7)7mRir^hZbiYtzD(%IFM0c(Uz?`VhbC+KCL(=$TJqHLaG^{5|_QO z0&sn6S?%@Mj^aUXWCarIh@B<2S$D3vw(6&Fxzk~z*C+6@Jb8sFQ)E@LrQCE5Nb(?~ z)MX8&lO}BSBQCVs_@#M`4kd9)l4oYh?(MMZ25B0)X_{+1265C*VWSt(j+6WuByWIu z=&Q4&E+fe_Nw~1vX2t7{EJ=}+GC~ckmz&kj0wRXK>+)RdSt?#knpom~Ot0tYcyB?y zkSqOj^ph1f-4zbc9;H@2m`M!>kj5Zi;4&Q@EsvNNMUFQ=z*vuroh;>DDzi6vt}D6f z@1=Vyq&S>~*x5nqta47eVaRCyf^)z`*g^ie;`m}skZc?%**beJYaFpSBrPVJ4X<6y z${BL;8K|8DD~?2=Zylb=%IBFZ7^z6cQ>=~R)aBRG;eznua@-K} z%{*&;wuW|KgspDMW+!1QZ4WhpVR3ahl7MW(&sVr zZ-U)3NM#OOgz+0(vQ5r*9mj&0Tf1L|q%&YtiuNlgU8(a#_!dOqP4QQ$y58Vku67iA z=v{Q2$8*~|_(r&Uc81Ux6k)5o!B(^~@O?$D%62kE#fzEtTUoHZD)n%Rqf@7d(mt13 zZC1LD=`dlgVo6=XOm&W_$}H>MD+FGXi!DoRE%z_ZlcYQJ(k@HQW9;-d!6;L+NBCNl zV7xe27Q^}(8E3sj9-^1b^&N|2mxxFWzD*D@q>_-T#YPdH@o4x4&j`nZFsIVsWTnDx zdzZA=V`g@m8TAM~LnAb-wh|6C18T9MZ!KH>7;7Uc+Mw>6kvb9}Rl)UQQkPKC1`XdZ z9}35UVR~H=Vbi8O)$8MXa4bE~?CzIFJX%H_m;IwWUb_wBwm9DV8g=cAdc9PUut6lV zJd;*wxHh+@XYu4#bmt4O_zp-rNN|{)_7?r_47JJ@CwIP#BrV1@f@s!=o~Yjnz_IS* z%-Ef5$q02=?j!cz#g+s;CBeB>xDjN+E-3g z4ClMcB;77aZ;IJUh2x=RrL{sc6pjYdbkjBFD@~T;1e^3Y@JssZr1e-^kp_LUDah-NNqY;zc5Smk{xB8;(6JCCAE$qAH5fG#MWlN ztCCY>8FMi-8}>?ZOtJb?7I!>)jA}M5OMs9V(Y;-u!y_id;0TGCsA9y!nktD?JRE^o zFcnC9*}B9y5RcTRq-yX*@ik&Ub)^{Me6SP}BjtHE;-RLRbo{lWZC>_{Jx-Nj}o`rBiH#94c;b<2f+s$S%_z-^=Vi2}v-n_gA3qe*^iKK=gs1{e|ns z=z&Dr8M4$*BNtYosy@c^Fvc4IuIEMc(uk@TQZtn?n=RY!+CF_3Q}ZfRO=aYM!wyVo z+a9U2H2oT(@%P}up|sO3Rvk^hPG~%u6`|@6D_MYkYPs0y;)`(7vsBw>P%SUvtaMD1 zbN=mAG~!fxi9nM*Fcg$cv&eFIk~zD<(extXTxKujrim!5`G>jUA7|07a3o#cw{9a- z;jDirf^ti`OvOccf-1o^PT`tM^sVJ$tAl3@C!39da_A>Lw)^Wi$r@_xLVC7FATc$s zS26_zAwf|hTcPi&)O<_D^sp`+c!3p%)fCbOict4^c#_eRf~Cls?QnE$6A?$v>tsEF z(`?-0AU0FE=P*y>AOYS1rdYn|MT1cA4BR?rFo_}Q2+Ii^^8y~MO;fKos8p*2VTcjo zD_g5Pwtj{C=H_|(!jZw~d;n90FRWkYYg?8H>S(diXL((2x z$)G%+I)1rrSTe#tZCYkbIswq|nMnDAuE%|K_=j~%FkwQ~C;Q5O=o%iZ!}r%y%-Hi4 zjbp|7{9-%n={_)TS@M!>Q0RrEhfd;V?q<2$<5!=4p1SYxfxB*JHq117H)?Zs)6z@3 z7=d$LkI!!zj#UG`X&$^0KxYc#sg}>zc06ut!h1dtkG=Ls8uUe!Ysk}m5v30!{Is)H8*opBm*iQ#tt2_CGZxi`;c+4E6$Mfj{t(H4(8yvMlL^hwfN zvBCWmlXV0L&^$r9r7<;6?a)6&ouzP`8neh8{kS?>mePpAQ^H9YF+gCd?AaAHIvAVh6{Bx(wYG zp}TWnx`ilV4y`#~S!`^9?P5fzx;lSP$e}RhTc$d>CLcyg8iamg_}Y$g*jL_Bhb;a= z1`U5bHhgJ2`+k2tv^l8dg%3#AI|92xn4ZUTGr&R7?p?_zkru$n(wFp{Pp^L$=oGg76lt=OyLT<9!(mBnh^FsG z-4u4`izJ<8qQ-lO^;QsX0O)={%HuUsT(X4~$-t@(&AU?@r`dNGWBXUIsmqVCv^p^x z+Y!|e+T9Au6#ecF8&|gQ^74{{A9b{Qv#2i8Yj3dJUtzA1GF?yeMA|kV^jXrR3hqrP zO1kr9Qm>VD?dm`i)L$a1EDiYYUawDRv)pi!Y*5|&490GuVsO!Iz`M|R4ZHQFY(lT( zDPb|F7_@&oa2LJy3LD9VLGT%3Fu;zhktVm})MI!39GiYW1GEUK6{>;YYBkUkU@fk5 z8RsQv>>x^z$Rh3(5a8&4yQ_#I7BSgL6A5^AWAaZ zG)^qr?SytunV)ilygI55BU$1V&!mK2l!L+Lz;=i_67|u1z}Jf$yEY4$A0BIItHs)^ z2k7-3En|s@h`lyhu81snryIrDdehd?YIg{OV%_U3Rj$F&?Pdce>m2P)gcyseLvZZ0 zW6+dF)gz3vuH5q+VQBHOxhBubt!nX;EPq?zSVFvLH>^IuHu7N7YW-PAyS+3+m22z!IOkIb;NN>lxWF;V-WEW$(BSu zC^>*Rg~ghA9>(}NsNu3}^0)C(JS%vS$u)JIWl$YW^yYC35+uPd1b26LO>lR(xVyUs zclY3K!3pl}1id)H-Qn`TZ|&ArZPo6l`7%9I-P3)#rq6SJPq5>+ib9l`M*`O($|SXl z=IC*FHQsbC<tYknYLA=+^HL5&MxSG3fNrg5%m`@2;|4XN z!5tIV&Mb?XTYr5SlQ)7YuE!v{JnI{EPN&dcg|NXs4YId%H*p%V1{@zEAD*Lfs_+Tf zmf!uy{;9$;M@`|C1C1e#_Vnc_7QYbu1cS>}8tgswz^khHd7orh-!3;_k!7T7L*VZC zFa@(&h4NQ7N>%n%>M{L43?#@!w18i~?1=Hrkhp{L=o9)eQ?$oU?NI`8leEEIe(R%` zP=08Z?SWc;;9qVrS;f!eP3^a1!Sa4D)39NR7t_jpX48aa6zE*dX{W*%duHQc&she7K)FD|#M1Fc0+j2HIW2ub~%|aMV1< z0ok7UXFA&Ig0-y{{A8pj3LRLOSI?Wy;h1qv6mv}t`so>XE`Z?-^ySuaYjeM?x zIZ6*b`2|CIlh~8MvhH6OSanN0=_&3f(ZFk%99G1yioT=`L{g}Em*RNNpcG2uZ1*H- z)!09H2-9jZs+4lX_j0tVl2+T)ZOS#eFM`dVDaC3`B34M0?Cd3~qQ6qb4j_!w@0|OD zFE*%}#9mVXynp}JdpOf^+iQfnSMmBaxSOL=C1Yiz+44Ms5MFXlGmQ|kboCNP@^_a< zji7kRN2!UMM@kR3=W$R(Jl;~577a-rRKqS2Dg8&sMBnt%sR-ni`bHDa<);CA1 zIgDp6e$YY%Ckj9FsLV>Fc6bWQ%FBWPx@9#E8z}bG{hVA(%cY2~i%^_mpu_C^yCY4U z-HSrZxE1|uD!AFvG(>RfTwYpOLkI6Gfin8rZre>6@oJg1?CF=?iq=Ct%S;v=Un^x! z$O&f}OshLY>D3x5Fu6iYL5iVb9xY4Q51=0^ski^q65d`_-D-B2t9p z1Gx}pZm)bXK(}lo*%_jNf9VnbiPj}a&wBx+U}B&_F6|TAE+;l@1w&d`78NUSnA{J$ z)K(?m3Wh{}&6Zo$ZjST;M|!t|N-6G93~KUvX6H~)DNCuoX`Y`@r_d|(m8N-5O+24o zzoPe4d51J21C+D8=1$b*);51x%gq?etxTBkO7y)zml_OsH4DZr6mwQKLjgmGM&W12 z*Sy;pO<0^nc41E3(ydl8NiXf7$kz?HnmP?0A7S_qU04HV_jhWCQ|eq;oQ;lMhde!> z%Apn6sml-7%Fb|Tf9G!3Gf|?d(iMuDq7GWi=h!rWP&u;Ik+6}(`Qd(N5kYbJTvI}s z20|hGa-b?*Ed3r(u|%W1-m!Qosh`rDNJaNT&@7%em?UD)M1n;1}+^oZ47Q=Ykm zhfqKlS}2|yvz2D-L$IpxnNtfi5dLlYll&ec8=4(V|LlmCFrq#3D6FG*d+F39+z|D5 zKe{0Cm@)5$1{c#}R+;V@a<|W+tNod)&EJn7`>Sac%bEND3GPBXBH|j3dlquKZrgP; zA{tj^&(5ZmUNRMj0Ye2Ma!{lJ&+vFr7SnMYQmj%}-?`|$kpi&@e&30AgR-i#*C+f? z0KuBy8R@4J=bI5)?mw@?cuFaK5lBvNS6ddn0W!X)3Fnh)7o<#>6eGSx#4QhOKbO1- zMpxX&j7^3JhSb@9jh@p}O|v}^d~hu+w5t^q#D{6E&U7J_U$v#3zHcmGo*`VBHAmXI zao_4Z9fi92g6PVtvmO&@+UxRF$LmUPA!qg0v>-jFjU?~afW77RK6ePLM2STFVX9$Z z4UfB{0kXvNjEZfFDPBMtnoNYxi0v`Ma7=JMlgkL(O?452B<5*gOcm&i{WaEBaxIcB z({GUrn+z=?=q{x9C1}N=|53m|{H8DeC!sAyJSS{{9CA5)K}0%RQ4fZQUdOV;^1ji& z2q(u7-4@$}SYk|bf*vqFAx#oEI;Ure!-)AoSG10e+e%L|p_H9Uf2L!D|F!m3)(;#Q zlxd`hAtE4$n>K?Fr7lg_*PDXhx;iq!a;Qge=dFp^PH>R+aSS!u*vf=B61&<@nf`S3 zf;vMClqyEOV6~X4!ktxtaeaoD0Vh#vD)5%OIV!Zz)1d#tv$eKwjDhm0_93?3m1T}? z0M&hiO<;o|BVJiN8E%Oz z8&|FDBS#!tMs%LG^b{eL<*6UxO5bQ7ln$+%Y>Mqn|Ew!4yk4NrFo`8m%B$5eW^4iZ zE1O+%PYmSf$MVERP&$&X*fjj#XNXe34{76O+evujC*jgG(0t%?Ia81HSR6<(#4!D= z3@rk-sb7%oY%>ak&ubthqKDw}+=W%cE%utq@wna8c3G8hfQL$&f|a2kXB2+;8+Ak* zTld2sYU?&TPAv-clL{RUaD8PS0e(sgsw2ac)13VD$)d0@jIErrCoLwMjC>`?xrhej zK$ei2gZ>Ag6mMf9B~nR#KMa7noGUd$>yZw@E&GCI1`{DDb#Ts#jUj^mcc11ANSuK$ z{zKf#h}}S&lF)k+eOdv(DH`z$|5Lx|>;>Z0JB7;~s>UMWCHT=vG#A02&8?Pt+!fZdd(Iz#NI-nSR^cQy#NT3tp zM4g-~hW#CrXkb_mK%)TlNWmDOuj9QR8*8Ts)o~_h-!}XNr=&=*5Chw8>k%K=SzS_h z%Ub&lkpoz=&G^Sy@)K{M-2+q@tqSVX!{32q1j?Z${L?H18Af8A+7!QDU%{vXQ}H8y zJopEmWMu?mpb?<8Y2;6=c&D~BLKg754Duj(1FJJ5PRePk9`4?Vo?Yi6|3$@wAC$hi z$U`LT91(lt(OY(M$i#$lj3wfC1k#eko>9lnMK-fy6S*%iXC>RU%Evx(_a^k#!Qp^2 zkRm@ir=vMmd`LT_wz#Qoc9fJaNH(*)Ssql!-@Z$sWQaEEO*|6!5EC-YnV3Ylu*RD} ziTF?jH@oM?wK$te2C&`}e7o}h(&RvCShH)E53DWmqLKyhD=*Jt|7-R;x6CaL8<~o` zp4siTzjUiB%{9bXNEChqOo%)v!;5EYO&o@wz59KzPEz=+qn=$u!>VME)KMjq`^5E2 z(QT}kTwGKZ{gu@_{R@s{c19KGm~nvZ<*l`Ip<^>~{@z#^#EMo)URVpf+oWK@@Nasi zjOwlWwXuTqO{or0W-q%QBHEZwAQu#Kfkk37uz@TCKm*)z{j|*w@ zQ&QAyM7A;cm~cyN%!woZ#14s&CSdX2_{l|Fw%vF+jac`K0xJ$63{^cw_}$77y!nMi z07?(^JDkbc{LU^nF^#t#f$I12ukAuCgjsrBV)BAjpVlBXQrk3hCjCpV-Gkfh2RZAn zGE-;KwXn!dO4S#k6X;C_-)jX`XB1BKt2VF$*h%|B_i#8>B~(FErcJh59f?1}#?`<= zRKx?|*XAV2b+@Td7b?1QWU6WC3}S91OKA0&%^`{sS|v?>u;hf$XFO`|2Y#P?vM<;A zE5T7IJ!iUP#H$3cQeXN{7)0#|7Uvx5fO{7-_%6iEjyEnil#70~tVE0Yej8eJJqG71 zRXAEBwJx!$wJ&EdRz0c0tFaZQgs`r)Ua^})$rqvXKP(GZKL6=vUa7?y&mB-l#}jt1 z7nk;k$$nJvkKl6*J+$+6tp2&ZLZH@9`pU@W>b`Q5NyD~-?@B~iB#C!=jj$}gdPjJ+ zFlU3icRMe{C~V=?##T6ztyb$yjB`MP>*V>ORm6N!=BmJjJ~sX1mC;_x-X3K+(3tR~ zYs^j^!KbzicA>#gN@Y0?cbjg7U}>nxBY!lJrrl0kqH(O)pxyOUEPY?$2FoYaEVL1p zs<&>+=<$Qy4b9DTs74z%} zE30p>>`WVC@KAT-JdK*`*alrb=WOp@JiSl+9}TKu*k6ShCP@rFHnvG8cO8fY#r<@IO9xARIO^=5sZfaw#_mGFa+>n+vC?C{YY&8YEC623tq|j;I zszUanZ%|)U`#xF!yjIRW(^V67E?d-cqKY%6_E?5x*Qq`FcMZHrt-5K}Yh1#XW7m1; zR1y*|e_QZ3ANe`tLTu*E>U`TL`|4%^)SpJOQL?T%prf&|W?cTLkE4)nu!>2{;9=#t zzw(PGy}Di+c|;xV!YKgMvayXV<~m-p7T_+Mxwl-?Ti0Z@0LUckl8?R}8&rLizyU(~{ zwND+Jfbc8p!gLys!d1yo;UOP;Go$T%;ri*qrSzbaSdxydeQ*~~r}yvg$x|4s79X4^ z2GMR$asj)*;1{rk0-NA4qBXf&>t)f|M8=uwqOE!zN6Gs6f_3wiSwzvTE(cwwgi~nq zMb?bam;}Gptf8(!5~qe~e(p*w2=hy>4XS(&h1w4>BZelo2x?l*mP%KjrHwcZC6bw$ zT4RJv&jQ47&$jx4$YiVo5ZFoe;_7;O*qnR2(k-Yo;UjH|{o$8?C8}2Koo) zkL|1Z^f3+%aFe-}J$tr-W99cHRo}XaR7Pp$`Jg|1-T z3()1UM+dXXlV?|A9K=&fyatQ?X&S__a6Ofb`X&SuE)LRkt#_Ny)4aPpg$w72gT!%@ zkG$S8$X#b-+H8+-M;ltk_)@SK^miTKIBq%+54xXGJ-E-b8Y+fLX$`h9tlO#w_UVjB z@BwMkD|>=9Y#rEUKx*9&2dil(lZKhKmd16GvIl{#)A}mv;0`sZ+G+`Y?B9d8^53wcX6n;9Ia ziG-6VgNSOeh1x%3BL*kaJCPwEC^@bEQd!BG^5<-a7O_#3NIGx+k@wuc!s`Vho@AId z{+`Ax{s}AZTkiqgoO_Y&NPTz=iV5m4`%`!P5#b<`GM%0tCxrrU%QjEPEcJ$)?eEm? ziDph{nY61xYyzWbG`212W9hD)n>1K?TUv% z@*k+@=R(u%b6eKC_q7M!0RGypfUc0y9|aJJ;R~knyoEp0!6K$Sry7$%jMqaBu+>g0 z3wKQyc{5IWzUl5~z28XQZ+*V@jbztq7#>N1@@0BF@?zQX=@SB*Bc@phUqZVxb~FIS zziiBQ-aG=AoO#XkS)xV5nC40$C12z2HHP)pB?6bn4Mfa9#6OvO%b)Wx{^e!;x?p0( zx8|HIvMA1D=vlfQxm>lnsd#x;FG^M3y2Vli%tSTKya>sv>B3~!v7SjZt_S`Pa_lnJA?YIiNk=S?#A)8V@6wHT>&9k>|Kg) zZd|bLn8^vaB~Ss{h^U19vp$^L-#K-ge2%q7U%QW3JcL=Mrp^07?+1K}{#LlvpFQEr z|1hjmO7@EkcTCRNyQ*k%D~SFG_1-ebp~LXK^l7_X2gO}l1S%Bai1Iv^ls&EG`-c~= zTg(R+YTCGJ+8MmYsybW9S~I>={Nk(WdS5qh`#4{aNZe~sw7YsbT2FXU3qi@t5EUdG zyFpZdy9G=u;d6T5bkb)iWeD*O&Ann^E*twivz?}=Me^x?5}@yQD11YwJNo<|kQ3J) zE{mJH-1jaq9!jB?AN@sC-5<5BZn@fL;(&qzn>!B8Y*MHnXxPAOcxLIm3 zr1$ZvcyzzYN?QNVnT3as4va1Xwdu2DS}b zh<`+)EG7tl$`gjs!`HLnz_ae^!@Z^)5bzl2vvn&RPkhDAn`MSQjS? z@rT<&Dk`1T+5+_n%dA%t*Vi8Z-?UB=1!W}g7tODgQ{Y?5x&jr$j~bIbrWrwO7bhU} zq}sBXHvpoDqY3$6FNdq2C0mpnxI zIt+AjN?eLgf8NdakIJ1mU2W}H$Q0{=`H8V?33SDkJNWl1iz9t#T&x;vk7^USN{^{B{;b-)LwY2nej z()b=N+>{cXuRJ{T{=`TaDOFAei~6kEF6nM>&ayu;b^y)mY5u_owC&SAr#(;cEy|>0 zlPULsD*|_|>qz$~L*KT(YxlY}plRsPT8y)N`-nDju3@Wj(P+u6)Ucsu8tr#L-_6tR zPQ`X4OMpo@&_U-HmmLJtI|E5h^+c`^=w060*)jM%M9&__vS3aI(<+E^QCb!NhRB&z&Z^~-#QMHiGJy<+< z+xqaMZhOz8)nIyjo)FeQY^Qd|oC1o(q_dNnLm99al#cgK9}mFnONFB;N)Z-ZH}(Wq zX7jPl*g5p%7hna7SyT<}0Hr^NS_plp?-~fxha4Ro9p(d9*oO)INjUszIs12g5(z{Q z^MtQoi1M_7h22tE)OuPK4PWvLRC0mV)=}2Ya@()ZSU9ik(~v!OPKxJEB9*>urr!IS zH*SMXd}G!4y`S=>rlsG5w>@CCEx^9$(G2>aVrR}!)b9uQCdQbl?q`I6r6Y!idRs(-^y`uUK=TX;zd?T82> z>tm3PCjo*-zkzQw!GW6ofnf5--)uY29ol(PwFuls<4GGEV2i>EQyL)*oLKwv@X z_Mug<;d<(F)F4q0-6Yn^Yt6I(Q_*5nQXY!bO z9S-aBR~$Wiq15xMYrfn&TiE^mt<5#DRB?~#xt#lB2fKODmS|3N3~N5Slu-Cd08US1 zjch)~+uO%W((XAWvI5-%BKs?i7bFA(eBF^11*0zIujyz982b3Mws^j=v~~j{a}N-z zW-dOr?iK2CMatUo6`VCj5fQ#0?UYN3kCKfzn@bRP@~8DYH_=zuxp`f_V%$PTVIk-G zd0p5L`0gCvJK?E0lkr`bkbT_XyX_5j@W*2US=4nzbPsOQ9dMqek0z6fqbM%j)+S)w zk)w;0f9e3IR357fYU^%Ud+Qb~&cLAV1s)?7y0@sZWhk3mE_Gq?;azWvPg#?z0OI+H zs6#5$HqI9&0abH0c8z|yih^;=J)Mfr$;Ta>Q;cffvBWn*%vhn}%>M4NGj9iUe~1geYJ_Kb!Tdlv!#|Uo7Q;_&@A-5)`6PC$ zl{~~>CfZ?Z&>>;r1DW9PiNbLH+C+wK3E(So<{BwLdis~yFaZb?4eH6110<|@5FxQY zxcF8*$-p?b=J>lMto36BBx%>d3U~e#HhgAd#)Hy2ZM+au6t|b*kAo?V>Ebe8QSu{e z=WDi&pMs&~>SpR{3jN$zI=*7Rw)Pil=In^?llZax{iiHTWG^L zu|s#di=x2D>PSAZ)z2Zna0anC&rL0Ib4SSScSSava`RO+|#A93}UcNcNn3K=a&xp zrW^~ZmcD+Mx`ba!ux^S=XMRk(1J=h4PvUUN9yjxQwfe`d6by(SZL5xL{M`hcZ+4b9^G63^;SF zlc>~Nm>!PprOKBC-A_aczoWC_xh3n$o2ZgyE7{-)-R*fez6!Fx%{k=mU5~hzYxLU& zYW>X!@hnZ!?fzFgm0(PR7s0k}1VJnge!K8~%+4^#A&Q!&Cfib|T_d$WoD=adpplVTxxj5l(ej(gZg%zW&@I|~v;Ir20br>@YsSQHs_}F5uWqrd zsHE-24=*B2BK z(Q4`F5#r$ysQK7fi^tM!s?WUoX+^CGUoA1oz!h!=|D29vJX2CpCdn~zcnj|rO4So zI;!O6TgYpN#vW9y8iN7}>hoYp-g-?r^1sYu%UzlG)xrBzXZ2aTC9s}U${=mf-%*x6 zdlEBqm7`kxE?1>XMOm_z;l$A^30Cgok?Hh%EAgTel{xeIt$6?Q;s-MBfX({Ce(bG; z+y_1D&cZjq*Q4G?v}U<53iR@@)EHf6c!T35W)zsd@1u8-Cn;F+5DS0t zRM7E5(ru8#UH(V>$E79#6ZU~R{68`94x~?T+jG+XrSKQ=m>;*nZ$%sm{#}2XpN*8P zUY7zsb5OXd`OeOnZV!H3cj7)U&1-7Wu7)hW)RKk`JC-^=#3u5fif z1gwxwim8y-@yTV#g!?7rLQgs8=&H)UWzsS4D|aOM73sy}HBA1vYpTIY8Ji}x{cp$R z9j~pnbm3&8bbKEmt9tp?(x4&t&pV~`DJJAfK$ho|x&sg1MypDA_AvJ%I=tEu@%sZo zZq3)}cC(obVHp3Rz7^DB;b-BG00S#eJ#~X?(VhFzA`(A5WdVye#cgTj_s>!aaK=Bf znxct>+=8u}5O6SUeCm|zVJey>oMK!sAGdOq>S*JI4=}%j!U;z96yJ#Xe@Fg^`GK8L zV`5%e5ViTw#Dub3t-@VUVQORi9pg{L@_~F?8@EwJt{B#c=M=uerVk1Gg3zY@c$1-D zPI2?p?sf7cNl$F7OWCn)-M|K_g+~6t-B8`$S1}~C%BYocqkh{8f%WJ0Abx8}0itJPZiEQdK>2FEoX&DMCTHPE5E_uiY`LSH7IvykaVs?6?-Ynfr49 zV8*827Yx6=%sgl9Fn8%LLlB&e@)1`N-~b*Pwem{Dt&rB<++3NuF@wE z&Czn{RfyF|v-Q^^)}N3vaw`d?s^6`x+7O^`Y;=00&%4CV1ZfZ1m2`!Rjg*A&-EEzK zkJF!X}Fl>fj;%a1|c06N#DwxC*L3o>{6f=}A@28M;QURJ@q3HI?Bitj$@^AL0QGgF4eL@v~m#K+wl_0%a=U*Cf2%O@;-k1w=LGe zn^exkCe%2V1<9G9et(gn{m3Vm$>xZ;!TSN`j{t$fHmK?pae<$o)EIaEoEm z)uY_X5`VRAdTp>YO0Pq#zeeOlq@fGCX37gj@ljl(cS}4yv!U)aEhh$^oY4yOgg3En z*05U=Scu9R6SD%WgtO0#DpYNlR4Nvmg1%ut?QY1XP_S-3a5)9QDxR?bGF4=bd+}2$ z%xqNMS6bU{`gysS>k@`#6(xkyJF7mr$lhzZm!pgtSE{=Xq4i~i2>*P$BBmkHSh~D| z?P+j?{5@=V@P8UX2l_I59DPmnvNtmPsvvws+8n$a0{8P8e{Gm+;<*}GiXm^#7-wye z$2$IG68Y_fgX>~`T`1iGz(|GZ*~THIR2{O2AEP;dapLI@pFDFpA*Rj1^+P5ZV6`F2 z(a){|^CDEIo@!q;l*)djE$9(hzTVW!PWDDHe!2UKTmPUwLJcbKvWG&$Crqm2@`c+$ zRDMSeYgIdK-X66Z;dPq%&p^YZSnG??Z+<342Fa#-{ZI8cw+qn~T0?`uiILFbe^K1U z-1uN!7Y!&cst-j-6f6>09W1`)q=Y4O^gq^SodzKO3b|{+QDm`7i#Na9ehHg^aI)hb z9%?IH(k1sX0u5oi(JAV1Nil5R+-Tzc?<_=?jJp1p(*j&u2`~RtWT(TyU*c9Jn`M{k z?@)tCb)3ea(4X@+T2B84a5ypsG7%_F@zxo0%gNV#>;FS@*rMxfZZ&HU&jd02SYJ-% zOjFD^wlE{Mu|wQph7&?~Lz{i?h5jz%$2oqAH5=wj7JrESznv$p1RRXrt%8ytx2EUE zir`X>d`-sx?(&C4l92*3LF?O}tFfymJS~nWUMD|h8AOhw;6Qd-Q2TCDE7zpHU{tX6AK3{k*MA zWC19?8x{Vdq~44-W88EgmO!TcIotW#_hdzWzgEdwPRY`7 zq7F~1>QO~1N>y&4#u>IYMim0L)`$+mTis73(?_hJK`}bc(lQHVx}`q2KsH0v;DLn7 z!hG-FzyXDs*{IOfj-7uw-%cJgtQY(4SM~q&hEeD3Z|u#~H+5U(y2VFdIRpj3-e`w{ zxydqi2$jUs4Qr5xl_bgYiQDB@ar4kJ^bdW!_yvLRz*7vzMqfU#zvFsuqCh_*R z|H{Juq8uAJW+>Z9iaC*zY?x{PO8Ew|8$}4p6ERAe^-%LG0q~X5jk{73zp49~b?g`? zp;s{rOx(E4%uPEFFn+puMe0F$;YsK=SdBhSb+ClQ@knXFFhR^^8}@-T&bx7t?kW4i z;lIsCwt#iP&k76iQSeb5uyq8s;3G8%!r)zD;!d9`*yQ`LwP%QM1a%Z<1T{M55Ym%D z+EE7q4b`f##WO>Cp5TkA>l!tS)1v#f7 zY2Q3o=}MtFU>%HndH;qP_hV|t>22$k$N4cttu(Fosen7!7Ji-jxVq8p7@kS+ zmWXhIzF2k*_lbjyMD;+}2heGSLh|wqu}-VnS7(G+r}0r9)e$0rvNFHa?!y5-LncT! znZ^FANW1BcgAe_XO3jzJ&o!<&Vcv9(!iIdY$vJ)&(Y=##9mcHR{~Qc=$B!)_&x2$X zH)F!Fz0^_-IVFXMQ<7GZ2~>vd9~=YU6(#UIOc8HuhZsyO$2E?*?oBoCcS|C?4z-O zs=J{7!6r!yza&mRo@W9JNJi7!d6w`{mFqSv57qvn% zkl{q6i3jgWZ$}08iML}sIjO%(_tF;7_m_$KkM%)tj;qT@tGf{(sN0}%!a#py3ZOVt zIV=eNO;wTv_sOl{FqmM8dzZ<8;Q0fV6`kPzuCDnMVvk7aDyijaYV2yp2Xr=rT%g!k z*f Date: Thu, 17 Oct 2024 12:37:00 -0500 Subject: [PATCH 10/11] Forgot to runder MD --- python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/README.md b/python/README.md index 77a1c10..b51a59b 100644 --- a/python/README.md +++ b/python/README.md @@ -1,6 +1,6 @@ - + From 2a71940d70117db91cbd01f383bbd637f71de0d6 Mon Sep 17 00:00:00 2001 From: Edgar Ruiz Date: Thu, 17 Oct 2024 12:47:07 -0500 Subject: [PATCH 11/11] Improves links --- python/pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 07e9a05..4a42186 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -42,6 +42,5 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project.urls] -homepage = "https://github.com/mlverse/mall" -documentation = "https://mlverse.github.io/mall/" +homepage = "https://mlverse.github.io/mall/" issues = "https://github.com/mlverse/mall/issues"