diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..1d75576 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,11 @@ +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: wallstreetlocal +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.vscode/launch.json b/.vscode/launch.json index 39fecf5..c2047cb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,14 +1,15 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python Debugger: Module", - "type": "debugpy", - "request": "launch", - "module": "test" - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Module", + "type": "debugpy", + "request": "launch", + "cwd": "${workspaceFolder}/tests", + "module": "main" + } + ] +} diff --git a/README.md b/README.md index 676ca31..911719d 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,216 @@

Cria, use Python to run LLMs with as little friction as possible.

+ +Cria is a library for programatically running Large Language Models through Python. Cria is built so you need as little configuration as possible — even with more advanced features. + +- **Easy**: No configuration is required out of the box. Defaults are built in, so getting started takes just five lines of code. +- **Concise**: Write less code to save time and avoid duplication. +- **Efficient**: Use advanced features with your own `ollama` instance, or a subprocess. + + + +## Guide + +- [Quick Start](#quickstart) +- [Installation](#installation) + - [Windows](#windows) + - [Mac](#mac) + - [Linux](#linux) +- [Advanced Usage](#advanced-usage) + - [Custom Models](#custom-models) + - [Streams](#streams) + - [Closing](#closing) + - [Message History](#message-history) + - [Multiple Models and Parallel Conversations](#multiple-models-and-parallel-conversations) +- [Contributing](#contributing) +- [License](#license) + +## Quickstart + +Running Cria is easy, after installation, you need just five lines of code. + +```python +import cria + +ai = cria.Cria() + +prompt = "Who is the CEO of OpenAI?" +for chunk in ai.chat(prompt): + print(chunk, end="") # The CEO of OpenAI is Sam Altman! + +# Not required, but best practice. +ai.close() +``` + +By default, Cria runs `llama3:8b`. If `llama3:8b` is not installed on your machine, Cria will install it automatically. + +**Important**: If the default model is not installed on your machine, downloading will take a while (`llama:8b` is about 4.7GB). + +## Installation + +1. Cria uses [`ollama`](https://ollama.com/), to install it, run the following. + + ### Windows + + [Download](https://ollama.com/download/windows) + + ### Mac + + [Download](https://ollama.com/download/mac) + + ### Linux + + ``` + curl -fsSL https://ollama.com/install.sh | sh + ``` + +2. Install Cria with `pip`. + + ``` + pip install cria + ``` + +## Advanced Usage + +### Custom Models + +To run other LLM models, pass them into your `ai` variable. + +```python +import cria + +ai = cria.Cria("llama2") + +prompt = "Who is the CEO of OpenAI?" +for chunk in ai.chat(prompt): + print(chunk, end="") # The CEO of OpenAI is Sam Altman. He co-founded OpenAI in 2015 with... +``` + +You can find available models [here](https://ollama.com/library). + +### Streams + +Streams are used by default in Cria, but you can turn them off by passing in a boolean for the `stream` parameter. + +```python +prompt = "Who is the CEO of OpenAI?" +response = ai.chat(prompt, stream=False) +print(response) # The CEO of OpenAI is Sam Altman! +``` + +### Closing + +By default, models are closed when you exit the Python program, but closing them manually is a best practice. + +```python +ai.close() +``` + +### Message History + +Message history is automatically saved in Cria, so asking followup questions is easy. + +```python +prompt = "Who is the CEO of OpenAI?" +response = ai.chat(prompt, stream=False) +print(response) # The CEO of OpenAI is Sam Altman. + +prompt = "Tell me more about him." +response = ai.chat(prompt, stream=False) +print(response) # Sam Altman is an American entrepreneur and technologist who serves as the CEO of OpenAI... +``` + +Clearing history is available as well. + +```python +prompt = "Who is the CEO of OpenAI?" +response = ai.chat(prompt, stream=False) +print(response) # Sam Altman is an American entrepreneur and technologist who serves as the CEO of OpenAI... + +ai.clear() + +prompt = "Tell me more about him." +response = ai.chat(prompt, stream=False) +print(response) # I apologize, but I don't have any information about "him" because the conversation just started... +``` + +### Multiple Models and Parallel Conversations + +If you are running multiple models or parallel conversations, the `Model` class is also available. This is recommended for most usecases. + +```python +import cria + +ai = cria.Model() + +prompt = "Who is the CEO of OpenAI?" +response = ai.chat(prompt, stream=False) +print(response) # The CEO of OpenAI is Sam Altman. +``` + +_All methods that apply to the `Cria` class also apply to `Model`._ + +Multiple models can be run through a `with` statement. This automatically closes them after use. + +```python +import cria + +prompt = "Who is the CEO of OpenAI?" + +with cria.Model("llama3") as ai: + response = ai.chat(prompt, stream=False) + print(response) # OpenAI's CEO is Sam Altman, who also... + +with cria.Model("llama2") as ai: + response = ai.chat(prompt, stream=False) + print(response) # The CEO of OpenAI is Sam Altman. +``` + +Or they can be run traditonally. + +```python +import cria + + +prompt = "Who is the CEO of OpenAI?" + +llama3 = cria.Model("llama3") +response = llama3.chat(prompt, stream=False) +print(response) # OpenAI's CEO is Sam Altman, who also... + +llama2 = cria.Model("llama2") +response = llama2.chat(prompt, stream=False) +print(response) # The CEO of OpenAI is Sam Altman. + +# Not required, but best practice. +llama3.close() +llama2.close() + +``` + +Cria can also has a `generate` method. + +```python +prompt = "Who is the CEO of OpenAI?" +for chunk in ai.generate(prompt): + print(chunk, end="") # The CEO of OpenAI (Open-source Artificial Intelligence) is Sam Altman. + +promt = "Tell me more about him." +response = ai.generate(prompt, stream=False) +print(response) # I apologize, but I think there may have been some confusion earlier. As this... +``` + +## Contributing + +If you have a feature request, feel free to make an issue! + +Contibutions are highly appreciated. + +## License + +[MIT](./LICENSE.md) diff --git a/poetry.lock b/poetry.lock index e52982f..c6124ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -128,6 +128,34 @@ files = [ [package.dependencies] httpx = ">=0.27.0,<0.28.0" +[[package]] +name = "psutil" +version = "5.9.8" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + [[package]] name = "sniffio" version = "1.3.1" @@ -153,4 +181,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d1c368c7738d962268583f2ad792765ce05533d80e7f46d25ca956c091fbb59a" +content-hash = "ea04a9ac1ddda76e842f86a8cf7f3bda865807feef6640f6e7dfe6b2dac76882" diff --git a/pyproject.toml b/pyproject.toml index 73d1738..2ac2544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cria" -version = "1.2" +version = "1.3.0" authors = [{ name = "leftmove", email = "100anonyo@gmail.com" }] description = "Run AI locally with as little friction as possible" readme = "README.md" @@ -26,6 +26,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.8" ollama = "^0.1.8" +psutil = "^5.9.8" [build-system] diff --git a/src/cria.py b/src/cria.py index 9d9ce77..fb8a3da 100644 --- a/src/cria.py +++ b/src/cria.py @@ -40,12 +40,18 @@ def check_models(model, silence_output): return if not silence_output: - print(f"LLM model not found, downloading '{model}'...") + print(f"LLM model not found, searching '{model}'...") try: - ollama.pull(model) + progress = ollama.pull(model, stream=True) + print( + f"LLM model {model} found, downloading... (this will probably take a while)" + ) if not silence_output: + for chunk in progress: + print(chunk) print(f"'{model}' downloaded, starting processes.") + return model except Exception as e: print(e) raise ValueError( @@ -100,7 +106,7 @@ def generate_stream(self, prompt): ai = ollama for chunk in ai.generate(model=model, prompt=prompt, stream=True): - content = chunk["message"]["content"] + content = chunk["response"] yield content def generate( @@ -114,7 +120,9 @@ def generate( if stream: return self.generate_stream(prompt) - response = ai.generate(model=model, prompt=prompt, stream=False) + chunk = ai.generate(model=model, prompt=prompt, stream=False) + response = chunk["response"] + return response def clear(self): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/main.py b/tests/main.py new file mode 100644 index 0000000..56dc577 --- /dev/null +++ b/tests/main.py @@ -0,0 +1,18 @@ +# Quick Start + +import sys + +sys.path.append("../") + +from src import cria + +ai = cria.Cria() + +prompt = "Who is the CEO of OpenAI?" +for chunk in ai.generate(prompt): + print(chunk, end="") + # OpenAI, a non-profit artificial intelligence research organization, does... + +prompt = "Tell me more about him." +response = ai.generate(prompt, stream=False) +print(response)