From b006fa6ec52ad43a54b597f84bab7d48e5a9e147 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 18 Jan 2024 07:15:00 -0800 Subject: [PATCH] Documentation --- README.md | 130 +++++++++++++++++++++++++++++- api/CHANGELOG.md | 7 ++ api/poetry.lock | 185 ++++++++++++++++++++++++++++++++++++++++++- api/pyproject.toml | 3 +- api/src/db.py | 2 + api/src/main.py | 83 +++++++++++++++++++ sdk/CHANGELOG.md | 12 +++ sdk/README.md | 42 ++++++++++ sdk/honcho/cache.py | 2 + sdk/honcho/client.py | 86 +++++++++++++++++++- 10 files changed, 546 insertions(+), 6 deletions(-) create mode 100644 api/CHANGELOG.md create mode 100644 sdk/CHANGELOG.md diff --git a/README.md b/README.md index 5151887a..fa621308 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,128 @@ -# honcho -Framework for privacy preserving personalization of AI Agents +# Honcho +![Static Badge](https://img.shields.io/badge/Version-0.0.1-blue) +![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2) +![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) +![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) +![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2Fplastic_labs) + +A User context management solution for building AI Agents and LLM powered +applications. + +Read about the motivation of this project [here](https://blog.plasticlabs.ai). + +## Project Structure + +The Honcho repo is a monorepo containing the server/API that manages database +interactions and storing data about an application's state along with the python +sdk for interacting with the API. + +The folders are structured as follows: + +- `api/` - contains a FastAPI application that provides user context management + routes +- `sdk/` - contains the code for the python sdk and package hosted on PyPI +- `example/` - contains example code for different use cases of honcho + +This project utilizes [poetry](https://python-poetry.org/) for dependency +management + +A separate changelog is managed for the sdk and api in their respective +directories. + +## Usage + +### API + +The API can be run either by installing the necessary dependencies and then +specifying the appropriate environment variables. + +1. Create a virtualenv and install the API's dependencies + +```bash +cd honcho/api/ +source $(poetry run poetry env info --path)/bin/activate +poetry install +``` + +2. Edit the `.env.template` file to specify the type of database and + connection_uri. For testing sqlite is fine + +```env +DATABASE_TYPE=sqlite +CONNECTION_URI=sqlite:///./honcho.db +``` + +3. Run the API via uvicorn + +```bash +cd honcho/api +source $(poetry run poetry env info --path)/bin/activate +python -m uvicorn src.main:app --reload +``` + +#### Docker + +Alternatively there is also a `Dockerfile` included to run the API server from a +docker container. + +The `.env` file is not loaded into the docker container and should still be +configured from outside. + +```bash +cd honcho/api +docker build -t honcho-api . +docker run --env-file .env -p 8000:8000 honcho-api:latest +``` + +### Client SDK + +Install the honcho client sdk from a python project with the following command: + +```bash +pip install honcho-ai +``` + +alternatively if you are using poetry run: + +```bash +poetry add honcho-ai +``` + +checkout the [example folder](./example/) for examples of how to use the sdk + +#### Build Locally + +For local development of the sdk you can build the sdk using poetry with the +following commands. + +```bash +cd honcho/sdk +poetry build +``` + +This will generate a `dist/` folder within the `honcho/sdk` directory. The +`dist/` will contain a wheel file. You can install the sdk in another local +project by specifiying the path to the wheel file in your installation command. + +```bash +pip install ./dist/honcho-ai-0.0.1-py3-none-any.whl +``` + +## Contributing + +This project is completely open source and welcomes any and all open source +contributions. The workflow for contributing is to make a fork of the +repository. You can claim an issue in the issues tab or start a new thread to +indicate a feature or bug fix you are working on. + +Once you have finished your contribution make a PR pointed at the `staging` +branch, and it will be reviewed by a project manager. Feel free to join us in +our [discord](http://discord.gg/plasticlabs) to discuss your changes or get +help. + +Once your changes are accepted and merged into staging they will undergo a +period of live testing before entering the upstream into `main` + +## License + +Honcho is licensed under the AGPL-3.0 License. Learn more at the [License file](./LICENSE) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md new file mode 100644 index 00000000..65669336 --- /dev/null +++ b/api/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [0.0.1] — 2024-01-18 diff --git a/api/poetry.lock b/api/poetry.lock index 6215ec17..a2f1d553 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -12,6 +12,9 @@ files = [ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "anyio" version = "3.7.1" @@ -25,6 +28,7 @@ files = [ ] [package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" @@ -60,6 +64,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "fastapi" version = "0.105.0" @@ -81,6 +100,78 @@ typing-extensions = ">=4.8.0" [package.extras] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "h11" version = "0.14.0" @@ -270,6 +361,94 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.25" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, + {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, + {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "starlette" version = "0.27.0" @@ -284,6 +463,7 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] @@ -315,11 +495,12 @@ files = [ [package.dependencies] click = ">=7.0" h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [metadata] lock-version = "2.0" -python-versions = "^3.11" -content-hash = "edfab4595c4cd2ed39f2565da6b90f682c7c9a9bc159b5d3f42603727a279225" +python-versions = "^3.8" +content-hash = "fae77b0119541a1cdda57bbe1eea3ab18c8f7118622ee3c932f58777d5f81ad3" diff --git a/api/pyproject.toml b/api/pyproject.toml index 3ebd6b80..51df0f77 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -6,10 +6,11 @@ authors = ["Plastic Labs "] readme = "README.md" [tool.poetry.dependencies] -python = "^3.11" +python = "^3.8" fastapi = "^0.105.0" uvicorn = "^0.24.0.post1" python-dotenv = "^1.0.0" +sqlalchemy = "^2.0.25" [build-system] diff --git a/api/src/db.py b/api/src/db.py index ddfcbde0..4818d6d7 100644 --- a/api/src/db.py +++ b/api/src/db.py @@ -6,6 +6,8 @@ import os load_dotenv() +# TODO check database type and whatever necessary connection parameters are needed for postgres + engine = create_engine( os.environ["CONNECTION_URI"], connect_args={"check_same_thread": False}, echo=True ) diff --git a/api/src/main.py b/api/src/main.py index a71c3ad9..cae0d759 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -23,6 +23,16 @@ def get_db(): @app.get("/users/{user_id}/sessions", response_model=list[schemas.Session]) def get_sessions(user_id: str, location_id: str, db: Session = Depends(get_db)): + """Get All Sessions for a User + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + + Returns: + list[schemas.Session]: List of Session objects + + """ if location_id is not None: return crud.get_sessions(db, user_id, location_id) return crud.get_sessions(db, user_id) @@ -32,6 +42,16 @@ def get_sessions(user_id: str, location_id: str, db: Session = Depends(get_db)): def create_session( user_id: str, session: schemas.SessionCreate, db: Session = Depends(get_db) ): + """Create a Session for a User + + Args: + user_id (str): The User ID representing the user, managed by the user + session (schemas.SessionCreate): The Session object containing any metadata and a location ID + + Returns: + schemas.Session: The Session object of the new Session + + """ return crud.create_session(db, user_id, session) @app.put("/users/{user_id}/sessions/{session_id}", response_model=schemas.Session) @@ -41,6 +61,17 @@ def update_session( session: schemas.SessionUpdate, db: Session = Depends(get_db), ): + """Update the metadata of a Session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to update + session (schemas.SessionUpdate): The Session object containing any new metadata + + Returns: + schemas.Session: The Session object of the updated Session + + """ if session.session_data is None: raise HTTPException(status_code=400, detail="Session data cannot be empty") # TODO TEST if I can set the metadata to be blank with this try: @@ -54,6 +85,19 @@ def delete_session( session_id: int, db: Session = Depends(get_db), ): + """Delete a session by marking it as inactive + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to delete + + Returns: + dict: A message indicating that the session was deleted + + Raises: + HTTPException: If the session is not found + + """ response = crud.delete_session(db, user_id=user_id, session_id=session_id) if response: return {"message": "Session deleted successfully"} @@ -62,6 +106,18 @@ def delete_session( @app.get("/users/{user_id}/sessions/{session_id}", response_model=schemas.Session) def get_session(user_id: str, session_id: int, db: Session = Depends(get_db)): + """Get a specific session for a user by ID + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + schemas.Session: The Session object of the requested Session + + Raises: + HTTPException: If the session is not found + """ honcho_session = crud.get_session(db, session_id=session_id, user_id=user_id) if honcho_session is None: raise HTTPException(status_code=404, detail="Session not found") @@ -81,6 +137,20 @@ def create_message_for_session( message: schemas.MessageCreate, db: Session = Depends(get_db), ): + """Adds a message to a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to add the message to + message (schemas.MessageCreate): The Message object to add containing the message content and type + + Returns: + schemas.Message: The Message object of the added message + + Raises: + HTTPException: If the session is not found + + """ try: return crud.create_message(db, message=message, user_id=user_id, session_id=session_id) except ValueError: @@ -95,6 +165,19 @@ def get_messages_for_session( session_id: int, db: Session = Depends(get_db), ): + """Get all messages for a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + list[schemas.Message]: List of Message objects + + Raises: + HTTPException: If the session is not found + + """ try: return crud.get_messages(db, user_id=user_id, session_id=session_id) except ValueError: diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md new file mode 100644 index 00000000..cd1bae43 --- /dev/null +++ b/sdk/CHANGELOG.md @@ -0,0 +1,12 @@ + +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [0.0.1] — 2024-01-18 + +### Added + diff --git a/sdk/README.md b/sdk/README.md index e69de29b..b68fee3b 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -0,0 +1,42 @@ +# Honcho + +A User context management solution for building AI Agents and LLM powered +applications. + +Read about the motivation of this project [here](https://blog.plasticlabs.ai). + +## Installation + +Install honcho: + +```bash +pip install honcho-ai +``` + +or + +```bash +poetry add honcho-ai +``` + +## Getting Started + +The Honcho SDK exposes a top level client that contains methods for managing the +lifecycle of different conversations and sessions in an LLM powered application. + +```python +from honcho import Client as HonchoClient + +honcho = HonchoClient(base_url="http://localhost:8000") +user_id = "test" +session = honcho.create_session(user_id=user_id) + +session_id = session["session_id"] + +honcho.create_message_for_session(user_id=user_id, session_id=session_id, is_user=True, content="Hello") +``` + +The honcho sdk code contains docstrings - see the full sdk on +[GitHub](https://github.com/plastic-labs/honcho/tree/main/sdk/honcho/client.py) + +See more examples of how to use the SDK on [GitHub](https://github.com/plastic-labs/honcho/tree/main/example) diff --git a/sdk/honcho/cache.py b/sdk/honcho/cache.py index cc0e2d68..f9488a0d 100644 --- a/sdk/honcho/cache.py +++ b/sdk/honcho/cache.py @@ -10,6 +10,7 @@ class LRUCache: self.cache = OrderedDict() def get(self, key: str): + """Get a value from the cache""" if key not in self.cache: return None @@ -18,6 +19,7 @@ class LRUCache: return self.cache[key] def put(self, key: str, value): + """Put a value in the cache""" if key in self.cache: # If the key already exists, move it to the end and update the value self.cache.move_to_end(key) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 2964858d..5a56aadf 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -3,24 +3,65 @@ import requests class Client: def __init__(self, base_url): - self.base_url = base_url + """Constructor for Client""" + self.base_url = base_url # Base URL for the instance of the Honcho API def get_sessions(self, user_id: str): + """Return sessions associated with a user + + Args: + user_id (str): The User ID representing the user, managed by the user + + Returns: + list[Dict]: List of Session objects + + """ url = f"{self.base_url}/users/{user_id}/sessions" response = requests.get(url) return response.json() def get_session(self, user_id: str, session_id: int): + """Get a specific session for a user by ID + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + Dict: The Session object of the requested Session + + """ url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" response = requests.get(url) return response.json() def get_sessions_by_location(self, user_id: str, location_id: str): + """Get all sessions for a user for a specific location + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + + Returns: + list[Dict]: List of Session objects + + """ url = f"{self.base_url}/users/{user_id}/sessions?location_id={location_id}" response = requests.get(url) return response.json() def create_session(self, user_id: str, location_id: str = "default", session_data: Dict = {}): + """Create a session for a user + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + session_data (Dict, optional): Optional session metadata + + Returns: + Dict: The Session object of the new Session + + """ data = { "location_id": location_id, "session_data": session_data @@ -30,6 +71,17 @@ class Client: return response.json() def update_session(self, user_id: str, session_id: int, session_data: Dict): + """Update the metadata of a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to update + session_data (Dict): The Session object containing any new metadata + + Returns: + Dict: The Session object of the updated Session + + """ data = { "session_data": session_data } @@ -38,16 +90,48 @@ class Client: return response.json() def delete_session(self, user_id: str, session_id: int): + """Delete a session by marking it as inactive + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to delete + + Returns: + Dict: A message indicating that the session was deleted + + """ url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" response = requests.delete(url) return response.json() def get_messages_for_session(self, user_id: str, session_id: int): + """Get all messages for a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + list[Dict]: List of Message objects + + """ url = f"{self.base_url}/users/{user_id}/sessions/{session_id}/messages" response = requests.get(url) return response.json() def create_message_for_session(self, user_id: str, session_id: int, is_user: bool, content: str): + """Adds a message to a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to add the message to + is_user (bool): Whether the message is from the user + content (str): The content of the message + + Returns: + Dict: The Message object of the added message + + """ data = { "is_user": is_user, "content": content