Merge pull request #1 from plastic-labs/restification-v
MVP User Context Management
This commit is contained in:
commit
ef3c417e68
|
|
@ -1,27 +0,0 @@
|
|||
name: Fly Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
jobs:
|
||||
prod-deploy:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
name: Deploy Production App
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
- run: flyctl deploy -c fly.toml --app honcho --remote-only
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
staging-deploy:
|
||||
if: github.ref == 'refs/heads/staging'
|
||||
name: Deploy Staging App
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
- run: flyctl deploy -c fly.toml --app staging-honcho --remote-only
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
api2/sql_db.sqlite
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
|
@ -60,6 +62,7 @@ cover/
|
|||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
*.sqlite
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
}
|
||||
130
README.md
130
README.md
|
|
@ -1,2 +1,128 @@
|
|||
# honcho
|
||||
Framework for privacy preserving personalization of AI Agents
|
||||
# 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).
|
||||
|
||||
## 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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
DATABASE_TYPE=sqlite
|
||||
CONNECTION_URI=sqlite:///./sql_db.db
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# https://pythonspeed.com/articles/base-image-python-docker-images/
|
||||
# https://testdriven.io/blog/docker-best-practices/
|
||||
FROM python:3.10-slim-bullseye
|
||||
|
||||
RUN apt-get update && apt-get install -y build-essential
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# https://stackoverflow.com/questions/53835198/integrating-python-poetry-with-docker
|
||||
ENV PYTHONFAULTHANDLER=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONHASHSEED=random \
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100 \
|
||||
POETRY_VERSION=1.4.1
|
||||
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
|
||||
# Copy only requirements to cache them in docker layer
|
||||
WORKDIR /app
|
||||
COPY poetry.lock pyproject.toml /app/
|
||||
|
||||
# Project initialization:
|
||||
RUN poetry config virtualenvs.create false \
|
||||
&& poetry install --no-root --no-interaction --no-ansi --without dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN addgroup --system app && adduser --system --group app
|
||||
USER app
|
||||
|
||||
COPY src/ src/
|
||||
|
||||
# https://stackoverflow.com/questions/29663459/python-app-does-not-print-anything-when-running-detached-in-docker
|
||||
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
#
|
||||
|
|
@ -0,0 +1,506 @@
|
|||
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.6.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
|
||||
{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"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
|
||||
{file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (<0.22)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{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"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.105.0-py3-none-any.whl", hash = "sha256:f19ebf6fdc82a3281d10f2cb4774bdfa90238e3b40af3525a0c09fd08ad1c480"},
|
||||
{file = "fastapi-0.105.0.tar.gz", hash = "sha256:4d12838819aa52af244580675825e750ad67c9df4614f557a769606af902cf22"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.7.1,<4.0.0"
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
starlette = ">=0.27.0,<0.28.0"
|
||||
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"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.6"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.5.2"
|
||||
description = "Data validation using Python type hints"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"},
|
||||
{file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.4.0"
|
||||
pydantic-core = "2.14.5"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.14.5"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"},
|
||||
{file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"},
|
||||
{file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"},
|
||||
{file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"},
|
||||
{file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"},
|
||||
{file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"},
|
||||
{file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"},
|
||||
{file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"},
|
||||
{file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"},
|
||||
{file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"},
|
||||
{file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"},
|
||||
{file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"},
|
||||
{file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"},
|
||||
{file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"},
|
||||
{file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"},
|
||||
{file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"},
|
||||
{file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"},
|
||||
{file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"},
|
||||
{file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"},
|
||||
{file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"},
|
||||
{file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"},
|
||||
{file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"},
|
||||
{file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"},
|
||||
{file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"},
|
||||
{file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"},
|
||||
{file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"},
|
||||
{file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"},
|
||||
{file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"},
|
||||
{file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"},
|
||||
{file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"},
|
||||
{file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.0"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
|
||||
{file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||
{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"
|
||||
description = "The little ASGI library that shines."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"},
|
||||
{file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"},
|
||||
]
|
||||
|
||||
[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"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.24.0.post1"
|
||||
description = "The lightning-fast ASGI server."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.24.0.post1-py3-none-any.whl", hash = "sha256:7c84fea70c619d4a710153482c0d230929af7bcf76c7bfa6de151f0a3a80121e"},
|
||||
{file = "uvicorn-0.24.0.post1.tar.gz", hash = "sha256:09c8e5a79dc466bdf28dead50093957db184de356fcdc48697bad3bde4c2588e"},
|
||||
]
|
||||
|
||||
[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.8"
|
||||
content-hash = "fae77b0119541a1cdda57bbe1eea3ab18c8f7118622ee3c932f58777d5f81ad3"
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[tool.poetry]
|
||||
name = "honcho"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Plastic Labs <hello@plasticlabs.ai>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
fastapi = "^0.105.0"
|
||||
uvicorn = "^0.24.0.post1"
|
||||
python-dotenv = "^1.0.0"
|
||||
sqlalchemy = "^2.0.25"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
import json
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
def get_session(db: Session, session_id: int, user_id: Optional[str] = None):
|
||||
stmt = select(models.Session).where(models.Session.id == session_id)
|
||||
if user_id is not None:
|
||||
stmt = stmt.where(models.Session.user_id == user_id)
|
||||
return db.scalars(stmt).one_or_none()
|
||||
# return db.query(models.Session).filter(models.Session.id == session_id).first()
|
||||
|
||||
|
||||
def get_sessions(db: Session, user_id: str, location_id: str | None = None) -> Sequence[schemas.Session]:
|
||||
stmt = select(models.Session).where(models.Session.user_id == user_id).where(models.Session.is_active.is_(True))
|
||||
|
||||
if location_id is not None:
|
||||
stmt = stmt.where(models.Session.location_id == location_id)
|
||||
|
||||
return db.scalars(stmt).all()
|
||||
|
||||
# filtered_by_user = db.query(models.Session).filter(
|
||||
# models.Session.user_id == user_id
|
||||
# )
|
||||
# filtered_by_location = (
|
||||
# filtered_by_user.filter(models.Session.location_id == location_id)
|
||||
# if location_id is not None
|
||||
# else filtered_by_user
|
||||
# )
|
||||
# return (
|
||||
# filtered_by_location.filter(models.Session.is_active.is_(True))
|
||||
# .order_by(models.Session.created_at.desc())
|
||||
# .all()
|
||||
# )
|
||||
|
||||
def create_session(db: Session, user_id: str, session: schemas.SessionCreate) -> models.Session:
|
||||
honcho_session = models.Session(
|
||||
user_id=user_id,
|
||||
location_id=session.location_id,
|
||||
session_data=json.dumps(session.session_data),
|
||||
)
|
||||
db.add(honcho_session)
|
||||
db.commit()
|
||||
db.refresh(honcho_session)
|
||||
return honcho_session
|
||||
|
||||
def update_session(db: Session, user_id: str, session_id: int, metadata: dict) -> bool:
|
||||
# stmt = select(models.Session).where(models.Session.id == session_id).where(models.Session.user_id == user_id)
|
||||
# honcho_session = db.scalars(stmt).one_or_none()
|
||||
honcho_session = get_session(db, session_id=session_id, user_id=user_id)
|
||||
# honcho_session = db.get(models.Session, session_id)
|
||||
if honcho_session is None:
|
||||
raise ValueError("Session not found or does not belong to user")
|
||||
honcho_session.metadata = metadata
|
||||
db.commit()
|
||||
db.refresh(honcho_session)
|
||||
return honcho_session
|
||||
|
||||
def delete_session(db: Session, user_id: str, session_id: int) -> bool:
|
||||
stmt = select(models.Session).where(models.Session.id == session_id).where(models.Session.user_id == user_id)
|
||||
honcho_session = db.scalars(stmt).one_or_none()
|
||||
# honcho_session = db.get(models.Session, session_id)
|
||||
if honcho_session is None:
|
||||
return False
|
||||
honcho_session.is_active = False
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def create_message(db: Session, message: schemas.MessageCreate, user_id: str, session_id: int) -> models.Message:
|
||||
honcho_session = get_session(db, session_id, user_id)
|
||||
if honcho_session is None:
|
||||
raise ValueError("Session not found or does not belong to user")
|
||||
|
||||
honcho_message = models.Message(
|
||||
session_id=session_id,
|
||||
is_user=message.is_user,
|
||||
content=message.content,
|
||||
)
|
||||
db.add(honcho_message)
|
||||
db.commit()
|
||||
db.refresh(honcho_message)
|
||||
return honcho_message
|
||||
|
||||
def get_messages(db: Session, user_id: str, session_id: int) -> Sequence[schemas.Message]:
|
||||
session = get_session(db, session_id, user_id)
|
||||
if session is None:
|
||||
raise ValueError("Session not found or does not belong to user")
|
||||
stmt = select(models.Message).where(models.Message.session_id == session_id)
|
||||
return db.scalars(stmt).all()
|
||||
# return (
|
||||
# db.query(models.Message)
|
||||
# .filter(models.Message.session_id == session_id)
|
||||
# .all()
|
||||
# )
|
||||
|
||||
# def get_metacognitions(db: Session, message_id: int):
|
||||
# return (
|
||||
# db.query(models.Metacognitions)
|
||||
# .filter(models.Metacognitions.message_id == message_id)
|
||||
# .all()
|
||||
# )
|
||||
|
||||
# def create_metacognition(
|
||||
# db: Session, metacognition: schemas.MetacognitionsCreate, message_id: int
|
||||
# ):
|
||||
# honcho_metacognition = models.Metacognitions(
|
||||
# message_id=message_id,
|
||||
# metacognition_type=metacognition.metacognition_type,
|
||||
# content=metacognition.content,
|
||||
# )
|
||||
# db.add(honcho_metacognition)
|
||||
# db.commit()
|
||||
# db.refresh(honcho_metacognition)
|
||||
# return honcho_metacognition
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from dotenv import load_dotenv
|
||||
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
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
import uvicorn
|
||||
|
||||
from . import crud, models, schemas
|
||||
from .db import SessionLocal, engine
|
||||
|
||||
models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
"""FastAPI Dependency Generator for Database"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
########################################################
|
||||
# Session Routes
|
||||
########################################################
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@app.post("/users/{user_id}/sessions", response_model=schemas.Session)
|
||||
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)
|
||||
def update_session(
|
||||
user_id: str,
|
||||
session_id: int,
|
||||
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:
|
||||
return crud.update_session(db, user_id=user_id, session_id=session_id, metadata=session.session_data)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
@app.delete("/users/{user_id}/sessions/{session_id}")
|
||||
def delete_session(
|
||||
user_id: str,
|
||||
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"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
@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")
|
||||
return honcho_session
|
||||
|
||||
########################################################
|
||||
# Message Routes
|
||||
########################################################
|
||||
|
||||
@app.post(
|
||||
"/users/{user_id}/sessions/{session_id}/messages",
|
||||
response_model=schemas.Message
|
||||
)
|
||||
def create_message_for_session(
|
||||
user_id: str,
|
||||
session_id: int,
|
||||
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:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
@app.get(
|
||||
"/users/{user_id}/sessions/{session_id}/messages",
|
||||
response_model=list[schemas.Message]
|
||||
)
|
||||
def get_messages_for_session(
|
||||
user_id: str,
|
||||
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:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
########################################################
|
||||
# Metacognition Routes
|
||||
########################################################
|
||||
|
||||
# @app.get(
|
||||
# "/users/{user_id}/sessions/{session_id}/messages/{message_id}/metacognitions/",
|
||||
# response_model=list[schemas.Metacognitions],
|
||||
# )
|
||||
# def get_metacognitions_for_message(
|
||||
# user_id: str,
|
||||
# session_id: int,
|
||||
# message_id: int,
|
||||
# db: Session = Depends(get_db),
|
||||
# ):
|
||||
# return crud.get_metacognitions(db, message_id)
|
||||
|
||||
|
||||
# @app.post(
|
||||
# "/users/{user_id}/sessions/{session_id}/messages/{message_id}/metacognitions/",
|
||||
# response_model=schemas.Metacognitions,
|
||||
# )
|
||||
# def create_metacognition_for_message(
|
||||
# user_id: str,
|
||||
# session_id: int,
|
||||
# message_id: int,
|
||||
# metacognition: schemas.MetacognitionsCreate,
|
||||
# db: Session = Depends(get_db),
|
||||
# ):
|
||||
# return crud.create_metacognition(db, metacognition, message_id)
|
||||
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime
|
||||
import datetime
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
|
||||
from .db import Base
|
||||
|
||||
|
||||
class Session(Base):
|
||||
__tablename__ = "sessions"
|
||||
# id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
# user_id = Column(String, index=True)
|
||||
# location_id = Column(String, index=True)
|
||||
# is_active = Column(Boolean, default=True)
|
||||
# session_data = Column(String)
|
||||
# created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
|
||||
user_id: Mapped[str] = mapped_column(index=True)
|
||||
location_id: Mapped[str] = mapped_column(index=True)
|
||||
is_active: Mapped[bool] = mapped_column(default=True)
|
||||
session_data: Mapped[str]
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow)
|
||||
messages = relationship("Message", back_populates="session")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Session(id={self.id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at})"
|
||||
|
||||
|
||||
class Message(Base):
|
||||
__tablename__ = "messages"
|
||||
# id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
# session_id = Column(Integer, ForeignKey("sessions.id"))
|
||||
# is_user = Column(Boolean)
|
||||
# content = Column(String)
|
||||
id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
|
||||
session_id: Mapped[int] = mapped_column(ForeignKey("sessions.id"))
|
||||
is_user: Mapped[bool]
|
||||
content: Mapped[str] # TODO add a max message length
|
||||
|
||||
session = relationship("Session", back_populates="messages")
|
||||
metacognitions = relationship("Metacognitions", back_populates="message")
|
||||
def __repr__(self) -> str:
|
||||
return f"Message(id={self.id}, session_id={self.session_id}, is_user={self.is_user}, content={self.content[10:]})"
|
||||
|
||||
|
||||
# TODO: add metacognitive data to messages
|
||||
class Metacognitions(Base):
|
||||
__tablename__ = "metacognitions"
|
||||
id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
|
||||
metacognition_type: Mapped[str] # TODO add a max metacognition type length
|
||||
content: Mapped[str]
|
||||
# id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
message_id = Column(Integer, ForeignKey("messages.id"))
|
||||
# metacognition_type = Column(String, index=True)
|
||||
# content = Column(String)
|
||||
|
||||
message = relationship("Message", back_populates="metacognitions")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Metacognitions(id={self.id}, message_id={self.message_id}, metacognition_type={self.metacognition_type}, content={self.content[10:]})"
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MessageBase(BaseModel):
|
||||
content: str
|
||||
is_user: bool
|
||||
|
||||
|
||||
class MessageCreate(MessageBase):
|
||||
pass
|
||||
|
||||
|
||||
class Message(MessageBase):
|
||||
session_id: int
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class SessionBase(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class SessionCreate(SessionBase):
|
||||
location_id: str
|
||||
session_data: dict | None = None
|
||||
|
||||
|
||||
class SessionUpdate(SessionBase):
|
||||
session_data: dict | None = None
|
||||
|
||||
|
||||
class Session(SessionBase):
|
||||
id: int
|
||||
messages: list[Message]
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class MetacognitionsBase(BaseModel):
|
||||
metacognition_type: str
|
||||
content: str
|
||||
|
||||
|
||||
class MetacognitionsCreate(MetacognitionsBase):
|
||||
pass
|
||||
|
||||
|
||||
class Metacognitions(MetacognitionsBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
class Mediator(ABC):
|
||||
@abstractmethod
|
||||
def get_sessions(self, user_id: str, location_id: str) -> List[Dict] | None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_session(self, session_id: str) -> Dict | None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_session(self, user_id: str, location_id: str, metadata: Dict) -> Dict:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_session(self, session_id: str, metadata: Dict) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_session(self, session_id: str) -> None:
|
||||
pass
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
"""
|
||||
Below is an implementation of a basic LRUcache that utilizes the built
|
||||
in OrderedDict data structure.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
from mediator import SupabaseMediator
|
||||
import uuid
|
||||
from typing import List, Dict
|
||||
from langchain.schema import BaseMessage, Document
|
||||
from pydantic import BaseModel
|
||||
|
||||
# import sentry_sdk
|
||||
|
||||
|
||||
class Conversation:
|
||||
"Wrapper Class for storing contexts between channels. Using an object to pass by reference avoid additional cache hits"
|
||||
|
||||
# @sentry_sdk.trace
|
||||
def __init__(
|
||||
self,
|
||||
mediator: SupabaseMediator,
|
||||
user_id: str,
|
||||
session_id: str = str(uuid.uuid4()),
|
||||
metadata: Dict = {},
|
||||
):
|
||||
self.mediator: SupabaseMediator = mediator
|
||||
self.user_id = user_id
|
||||
self.session_id = session_id
|
||||
self.metadata: Dict = metadata
|
||||
|
||||
# @sentry_sdk.trace
|
||||
def add_message(
|
||||
self,
|
||||
message_type: str,
|
||||
message: BaseMessage,
|
||||
) -> None:
|
||||
self.mediator.add_message(self.session_id, message_type, message.content)
|
||||
|
||||
# @sentry_sdk.trace
|
||||
def get_messages(self, message_type: str) -> List[BaseMessage]:
|
||||
return self.mediator.get_messages(self.session_id, message_type)
|
||||
|
||||
# @sentry_sdk.trace
|
||||
def delete(self) -> None:
|
||||
self.mediator.delete_session(self.session_id)
|
||||
|
||||
# @sentry_sdk.trace
|
||||
# def restart(self) -> None:
|
||||
# self.delete()
|
||||
# representation = self.mediator.add_session(
|
||||
# user_id=self.user_id, location_id=self.location_id
|
||||
# )
|
||||
# self.session_id: str = representation["id"]
|
||||
# self.metadata = representation["metadata"]
|
||||
|
||||
# vector DB fn
|
||||
# @sentry_sdk.trace
|
||||
def add_texts(self, texts: List[str]) -> None:
|
||||
metadatas = [
|
||||
{"session_id": self.session_id, "user_id": self.user_id}
|
||||
for _ in range(len(texts))
|
||||
]
|
||||
self.mediator.vector_table.add_texts(texts, metadatas)
|
||||
|
||||
# vector DB fn
|
||||
# @sentry_sdk.trace
|
||||
def similarity_search(self, query: str, match_count: int = 5) -> List[Document]:
|
||||
return self.mediator.vector_table.similarity_search(
|
||||
query=query, k=match_count, filter={"user_id": self.user_id}
|
||||
)
|
||||
|
|
@ -1,59 +1,103 @@
|
|||
import os
|
||||
from langchain.chat_models import ChatOpenAI, AzureChatOpenAI
|
||||
from langchain.output_parsers.list import NumberedListOutputParser
|
||||
from langchain.prompts import load_prompt, ChatPromptTemplate, SystemMessagePromptTemplate
|
||||
from langchain.prompts import (
|
||||
load_prompt,
|
||||
ChatPromptTemplate,
|
||||
SystemMessagePromptTemplate,
|
||||
)
|
||||
from langchain.schema import AIMessage, HumanMessage, BaseMessage
|
||||
from dotenv import load_dotenv
|
||||
from collections.abc import AsyncIterator
|
||||
from cache import Conversation
|
||||
from typing import List
|
||||
|
||||
|
||||
from openai import BadRequestError
|
||||
|
||||
import sentry_sdk
|
||||
|
||||
load_dotenv()
|
||||
|
||||
SYSTEM_THOUGHT = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/thought.yaml'))
|
||||
SYSTEM_RESPONSE = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/response.yaml'))
|
||||
SYSTEM_THOUGHT_REVISION = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/thought_revision.yaml'))
|
||||
SYSTEM_USER_PREDICTION_THOUGHT = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/user_prediction_thought.yaml'))
|
||||
SYSTEM_USER_PREDICTION_THOUGHT_REVISION = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/user_prediction_thought_revision.yaml'))
|
||||
SYSTEM_VOE_THOUGHT = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/voe_thought.yaml'))
|
||||
SYSTEM_VOE = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/voe.yaml'))
|
||||
SYSTEM_CHECK_VOE_LIST = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/check_voe_list.yaml'))
|
||||
SYSTEM_THOUGHT = load_prompt(
|
||||
os.path.join(os.path.dirname(__file__), "prompts/thought.yaml")
|
||||
)
|
||||
SYSTEM_RESPONSE = load_prompt(
|
||||
os.path.join(os.path.dirname(__file__), "prompts/response.yaml")
|
||||
)
|
||||
SYSTEM_THOUGHT_REVISION = load_prompt(
|
||||
os.path.join(os.path.dirname(__file__), "prompts/thought_revision.yaml")
|
||||
)
|
||||
SYSTEM_USER_PREDICTION_THOUGHT = load_prompt(
|
||||
os.path.join(os.path.dirname(__file__), "prompts/user_prediction_thought.yaml")
|
||||
)
|
||||
SYSTEM_USER_PREDICTION_THOUGHT_REVISION = load_prompt(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__), "prompts/user_prediction_thought_revision.yaml"
|
||||
)
|
||||
)
|
||||
SYSTEM_VOE_THOUGHT = load_prompt(
|
||||
os.path.join(os.path.dirname(__file__), "prompts/voe_thought.yaml")
|
||||
)
|
||||
SYSTEM_VOE = load_prompt(os.path.join(os.path.dirname(__file__), "prompts/voe.yaml"))
|
||||
SYSTEM_CHECK_VOE_LIST = load_prompt(
|
||||
os.path.join(os.path.dirname(__file__), "prompts/check_voe_list.yaml")
|
||||
)
|
||||
|
||||
|
||||
class BloomChain:
|
||||
"Wrapper class for encapsulating the multiple different chains used in reasoning for the tutor's thoughts"
|
||||
llm: AzureChatOpenAI = AzureChatOpenAI(deployment_name = os.environ['OPENAI_API_DEPLOYMENT_NAME'], temperature=1.2, model_kwargs={"top_p": 0.5})
|
||||
parser_llm: AzureChatOpenAI = AzureChatOpenAI(deployment_name = os.environ['OPENAI_API_DEPLOYMENT_NAME'])
|
||||
system_voe_thought: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_VOE_THOUGHT)
|
||||
system_voe: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_VOE)
|
||||
system_check_voe_list: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_CHECK_VOE_LIST)
|
||||
system_thought: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_THOUGHT)
|
||||
system_thought_revision: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_THOUGHT_REVISION)
|
||||
system_response: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_RESPONSE)
|
||||
system_user_prediction_thought: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_USER_PREDICTION_THOUGHT)
|
||||
system_user_prediction_thought_revision: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_USER_PREDICTION_THOUGHT_REVISION)
|
||||
llm: AzureChatOpenAI = AzureChatOpenAI(
|
||||
deployment_name=os.environ["OPENAI_API_DEPLOYMENT_NAME"],
|
||||
temperature=1.2,
|
||||
model_kwargs={"top_p": 0.5},
|
||||
)
|
||||
parser_llm: AzureChatOpenAI = AzureChatOpenAI(
|
||||
deployment_name=os.environ["OPENAI_API_DEPLOYMENT_NAME"]
|
||||
)
|
||||
system_voe_thought: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
|
||||
prompt=SYSTEM_VOE_THOUGHT
|
||||
)
|
||||
system_voe: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
|
||||
prompt=SYSTEM_VOE
|
||||
)
|
||||
system_check_voe_list: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
|
||||
prompt=SYSTEM_CHECK_VOE_LIST
|
||||
)
|
||||
system_thought: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
|
||||
prompt=SYSTEM_THOUGHT
|
||||
)
|
||||
system_thought_revision: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
|
||||
prompt=SYSTEM_THOUGHT_REVISION
|
||||
)
|
||||
system_response: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
|
||||
prompt=SYSTEM_RESPONSE
|
||||
)
|
||||
system_user_prediction_thought: SystemMessagePromptTemplate = (
|
||||
SystemMessagePromptTemplate(prompt=SYSTEM_USER_PREDICTION_THOUGHT)
|
||||
)
|
||||
system_user_prediction_thought_revision: SystemMessagePromptTemplate = (
|
||||
SystemMessagePromptTemplate(prompt=SYSTEM_USER_PREDICTION_THOUGHT_REVISION)
|
||||
)
|
||||
|
||||
output_parser = NumberedListOutputParser()
|
||||
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
# @sentry_sdk.trace
|
||||
def think(cls, cache: Conversation, input: str):
|
||||
"""Generate Bloom's thought on the user."""
|
||||
# load message history
|
||||
thought_prompt = ChatPromptTemplate.from_messages([
|
||||
cls.system_thought,
|
||||
*cache.messages("thought"),
|
||||
HumanMessage(content=input)
|
||||
])
|
||||
chain = thought_prompt | cls.llm
|
||||
thought_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
cls.system_thought,
|
||||
*cache.get_messages("thought"),
|
||||
HumanMessage(content=input),
|
||||
]
|
||||
)
|
||||
chain = thought_prompt | cls.llm
|
||||
|
||||
|
||||
def save_new_messages(ai_response):
|
||||
|
|
@ -64,21 +108,23 @@ class BloomChain:
|
|||
chain.astream({}, {"tags": ["thought"], "metadata": {"conversation_id": cache.conversation_id, "user_id": cache.user_id}}),
|
||||
save_new_messages
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
# @sentry_sdk.trace
|
||||
def revise_thought(cls, cache: Conversation, input: str, thought: str):
|
||||
"""Revise Bloom's thought about the user with retrieved personal data"""
|
||||
|
||||
|
||||
# construct rag prompt, retrieve docs
|
||||
query = f"input: {input}\n thought: {thought}"
|
||||
docs = cache.similarity_search(query)
|
||||
|
||||
messages = ChatPromptTemplate.from_messages([
|
||||
cls.system_thought_revision,
|
||||
*cache.messages('thought_revision'),
|
||||
HumanMessage(content=input)
|
||||
])
|
||||
messages = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
cls.system_thought_revision,
|
||||
*cache.get_messages("thought_revision"),
|
||||
HumanMessage(content=input),
|
||||
]
|
||||
)
|
||||
chain = messages | cls.llm
|
||||
|
||||
def save_new_messages(ai_response):
|
||||
|
|
@ -89,16 +135,18 @@ class BloomChain:
|
|||
chain.astream({ "thought": thought, "retrieved_vectors": "\n".join(doc.page_content for doc in docs)}, {"tags": ["thought_revision"], "metadata": {"conversation_id": cache.conversation_id, "user_id": cache.user_id}}),
|
||||
save_new_messages
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
# @sentry_sdk.trace
|
||||
def respond(cls, cache: Conversation, thought: str, input: str):
|
||||
"""Generate Bloom's response to the user."""
|
||||
response_prompt = ChatPromptTemplate.from_messages([
|
||||
cls.system_response,
|
||||
*cache.messages("response"),
|
||||
HumanMessage(content=input)
|
||||
])
|
||||
response_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
cls.system_response,
|
||||
*cache.get_messages("response"),
|
||||
HumanMessage(content=input),
|
||||
]
|
||||
)
|
||||
chain = response_prompt | cls.llm
|
||||
|
||||
def save_new_messages(ai_response):
|
||||
|
|
@ -109,93 +157,123 @@ class BloomChain:
|
|||
chain.astream({ "thought": thought }, {"tags": ["response"], "metadata": {"conversation_id": cache.conversation_id, "user_id": cache.user_id}}),
|
||||
save_new_messages
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
# @sentry_sdk.trace
|
||||
async def think_user_prediction(cls, cache: Conversation, input: str):
|
||||
"""Generate a thought about what the user is going to say"""
|
||||
|
||||
messages = ChatPromptTemplate.from_messages([
|
||||
cls.system_user_prediction_thought,
|
||||
])
|
||||
messages = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
cls.system_user_prediction_thought,
|
||||
]
|
||||
)
|
||||
chain = messages | cls.llm
|
||||
|
||||
history = unpack_messages(cache.messages('response'))
|
||||
history = unpack_messages(cache.get_messages("response"))
|
||||
|
||||
user_prediction_thought = await chain.ainvoke(
|
||||
{"history": history},
|
||||
{"tags": ["user_prediction_thought"], "metadata": {"conversation_id": cache.conversation_id, "user_id": cache.user_id}}
|
||||
{"history": history},
|
||||
{
|
||||
"tags": ["user_prediction_thought"],
|
||||
"metadata": {
|
||||
"conversation_id": cache.conversation_id,
|
||||
"user_id": cache.user_id,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
cache.add_message("user_prediction_thought", user_prediction_thought)
|
||||
|
||||
return user_prediction_thought.content
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
async def revise_user_prediction_thought(cls, cache: Conversation, user_prediction_thought: str, input: str):
|
||||
# @sentry_sdk.trace
|
||||
async def revise_user_prediction_thought(
|
||||
cls, cache: Conversation, user_prediction_thought: str, input: str
|
||||
):
|
||||
"""Revise the thought about what the user is going to say based on retrieval of VoE facts"""
|
||||
|
||||
messages = ChatPromptTemplate.from_messages([
|
||||
cls.system_user_prediction_thought_revision,
|
||||
])
|
||||
messages = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
cls.system_user_prediction_thought_revision,
|
||||
]
|
||||
)
|
||||
chain = messages | cls.llm
|
||||
|
||||
# construct rag prompt, retrieve docs
|
||||
query = f"input: {input}\n thought: {user_prediction_thought}"
|
||||
docs = cache.similarity_search(query)
|
||||
|
||||
history = unpack_messages(cache.messages('response'))
|
||||
history = unpack_messages(cache.get_messages("response"))
|
||||
|
||||
user_prediction_thought_revision = await chain.ainvoke(
|
||||
{"history": history, "user_prediction_thought": user_prediction_thought, "retrieved_vectors": "\n".join(doc.page_content for doc in docs)},
|
||||
config={"tags": ["user_prediction_thought_revision"], "metadata": {"conversation_id": cache.conversation_id, "user_id": cache.user_id}}
|
||||
{
|
||||
"history": history,
|
||||
"user_prediction_thought": user_prediction_thought,
|
||||
"retrieved_vectors": "\n".join(doc.page_content for doc in docs),
|
||||
},
|
||||
config={
|
||||
"tags": ["user_prediction_thought_revision"],
|
||||
"metadata": {
|
||||
"conversation_id": cache.conversation_id,
|
||||
"user_id": cache.user_id,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
cache.add_message("user_prediction_thought_revision", user_prediction_thought_revision)
|
||||
cache.add_message(
|
||||
"user_prediction_thought_revision", user_prediction_thought_revision
|
||||
)
|
||||
|
||||
return user_prediction_thought_revision.content
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
async def think_violation_of_expectation(cls, cache: Conversation, inp: str, user_prediction_thought_revision: str) -> None:
|
||||
# @sentry_sdk.trace
|
||||
async def think_violation_of_expectation(
|
||||
cls, cache: Conversation, inp: str, user_prediction_thought_revision: str
|
||||
) -> None:
|
||||
"""Assess whether expectation was violated, derive and store facts"""
|
||||
|
||||
# format prompt
|
||||
messages = ChatPromptTemplate.from_messages([
|
||||
cls.system_voe_thought
|
||||
])
|
||||
messages = ChatPromptTemplate.from_messages([cls.system_voe_thought])
|
||||
chain = messages | cls.llm
|
||||
|
||||
voe_thought = await chain.ainvoke(
|
||||
{"user_prediction_thought_revision": user_prediction_thought_revision, "actual": inp},
|
||||
config={"tags": ["voe_thought"], "metadata": {"user_id": cache.user_id}}
|
||||
{
|
||||
"user_prediction_thought_revision": user_prediction_thought_revision,
|
||||
"actual": inp,
|
||||
},
|
||||
config={"tags": ["voe_thought"], "metadata": {"user_id": cache.user_id}},
|
||||
)
|
||||
|
||||
cache.add_message("voe_thought", voe_thought)
|
||||
|
||||
return voe_thought.content
|
||||
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
async def violation_of_expectation(cls, cache: Conversation, inp: str, user_prediction_thought_revision: str, voe_thought: str) -> None:
|
||||
# @sentry_sdk.trace
|
||||
async def violation_of_expectation(
|
||||
cls,
|
||||
cache: Conversation,
|
||||
inp: str,
|
||||
user_prediction_thought_revision: str,
|
||||
voe_thought: str,
|
||||
) -> None:
|
||||
"""Assess whether expectation was violated, derive and store facts"""
|
||||
|
||||
# format prompt
|
||||
messages = ChatPromptTemplate.from_messages([
|
||||
cls.system_voe
|
||||
])
|
||||
messages = ChatPromptTemplate.from_messages([cls.system_voe])
|
||||
chain = messages | cls.llm
|
||||
|
||||
voe = await chain.ainvoke(
|
||||
{
|
||||
"ai_message": cache.messages('response')[-1].content,
|
||||
"user_prediction_thought_revision": user_prediction_thought_revision,
|
||||
"actual": inp,
|
||||
"voe_thought": voe_thought
|
||||
},
|
||||
config={"tags": ["voe"], "metadata": {"user_id": cache.user_id}}
|
||||
"ai_message": cache.get_messages("response")[-1].content,
|
||||
"user_prediction_thought_revision": user_prediction_thought_revision,
|
||||
"actual": inp,
|
||||
"voe_thought": voe_thought,
|
||||
},
|
||||
config={"tags": ["voe"], "metadata": {"user_id": cache.user_id}},
|
||||
)
|
||||
|
||||
cache.add_message("voe", voe)
|
||||
|
|
@ -203,14 +281,12 @@ class BloomChain:
|
|||
return facts
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
# @sentry_sdk.trace
|
||||
async def check_voe_list(cls, cache: Conversation, facts: List[str]):
|
||||
"""Filter the facts to just new ones"""
|
||||
|
||||
# create the message object from prompt template
|
||||
messages = ChatPromptTemplate.from_messages([
|
||||
cls.system_check_voe_list
|
||||
])
|
||||
messages = ChatPromptTemplate.from_messages([cls.system_check_voe_list])
|
||||
chain = messages | cls.llm
|
||||
|
||||
# unpack the list of strings into one string for similarity search
|
||||
|
|
@ -222,10 +298,12 @@ class BloomChain:
|
|||
|
||||
filtered_facts = await chain.ainvoke(
|
||||
{
|
||||
"existing_facts": "\n".join(fact.page_content for fact in existing_facts),
|
||||
"facts": "\n".join(fact for fact in facts)
|
||||
},
|
||||
config={"tags": ["check_voe_list"], "metadata": {"user_id": cache.user_id}}
|
||||
"existing_facts": "\n".join(
|
||||
fact.page_content for fact in existing_facts
|
||||
),
|
||||
"facts": "\n".join(fact for fact in facts),
|
||||
},
|
||||
config={"tags": ["check_voe_list"], "metadata": {"user_id": cache.user_id}},
|
||||
)
|
||||
|
||||
data = cls.output_parser.parse(filtered_facts.content)
|
||||
|
|
@ -236,15 +314,21 @@ class BloomChain:
|
|||
else:
|
||||
cache.add_texts(data)
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
async def chat(cls, cache: Conversation, inp: str ) -> tuple[str, str]:
|
||||
@classmethod
|
||||
# @sentry_sdk.trace
|
||||
async def chat(cls, cache: Conversation, inp: str) -> tuple[str, str]:
|
||||
# VoE has to happen first. If there's user prediction history, derive and store fact(s)
|
||||
if cache.messages('user_prediction_thought_revision'):
|
||||
user_prediction_thought_revision = cache.messages('user_prediction_thought_revision')[-1].content
|
||||
if cache.get_messages("user_prediction_thought_revision"):
|
||||
user_prediction_thought_revision = cache.get_messages(
|
||||
"user_prediction_thought_revision"
|
||||
)[-1].content
|
||||
|
||||
voe_thought = await cls.think_violation_of_expectation(cache, inp, user_prediction_thought_revision)
|
||||
voe_facts = await cls.violation_of_expectation(cache, inp, user_prediction_thought_revision, voe_thought)
|
||||
voe_thought = await cls.think_violation_of_expectation(
|
||||
cache, inp, user_prediction_thought_revision
|
||||
)
|
||||
voe_facts = await cls.violation_of_expectation(
|
||||
cache, inp, user_prediction_thought_revision, voe_thought
|
||||
)
|
||||
|
||||
if not voe_facts or voe_facts[0] == "None":
|
||||
pass
|
||||
|
|
@ -261,20 +345,28 @@ class BloomChain:
|
|||
response = await response_iterator()
|
||||
|
||||
user_prediction_thought = await cls.think_user_prediction(cache, inp)
|
||||
user_prediction_thought_revision = await cls.revise_user_prediction_thought(cache, user_prediction_thought, inp)
|
||||
user_prediction_thought_revision = await cls.revise_user_prediction_thought(
|
||||
cache, user_prediction_thought, inp
|
||||
)
|
||||
|
||||
return thought, response
|
||||
|
||||
@classmethod
|
||||
@sentry_sdk.trace
|
||||
async def stream(cls, cache: Conversation, inp: str ):
|
||||
@classmethod
|
||||
# @sentry_sdk.trace
|
||||
async def stream(cls, cache: Conversation, inp: str):
|
||||
# VoE has to happen first. If there's user prediction history, derive and store fact(s)
|
||||
try:
|
||||
if cache.messages('user_prediction_thought_revision'):
|
||||
user_prediction_thought_revision = cache.messages('user_prediction_thought_revision')[-1].content
|
||||
if cache.get_messages("user_prediction_thought_revision"):
|
||||
user_prediction_thought_revision = cache.get_messages(
|
||||
"user_prediction_thought_revision"
|
||||
)[-1].content
|
||||
|
||||
voe_thought = await cls.think_violation_of_expectation(cache, inp, user_prediction_thought_revision)
|
||||
voe_facts = await cls.violation_of_expectation(cache, inp, user_prediction_thought_revision, voe_thought)
|
||||
voe_thought = await cls.think_violation_of_expectation(
|
||||
cache, inp, user_prediction_thought_revision
|
||||
)
|
||||
voe_facts = await cls.violation_of_expectation(
|
||||
cache, inp, user_prediction_thought_revision, voe_thought
|
||||
)
|
||||
|
||||
if not voe_facts or voe_facts[0] == "None":
|
||||
pass
|
||||
|
|
@ -298,17 +390,16 @@ class BloomChain:
|
|||
print("Finished Thought")
|
||||
print("=========================================")
|
||||
|
||||
|
||||
thought_revision_iterator = cls.revise_thought(cache, inp, thought)
|
||||
thought_revision = await thought_revision_iterator()
|
||||
|
||||
response_iterator = cls.respond(cache, thought_revision, inp)
|
||||
#response = ""
|
||||
# response = ""
|
||||
|
||||
async for item in response_iterator:
|
||||
# if "❀" in item:
|
||||
item = item.replace("❀", "🌸")
|
||||
#response += item
|
||||
# response += item
|
||||
yield item
|
||||
|
||||
print("=========================================")
|
||||
|
|
@ -316,7 +407,9 @@ class BloomChain:
|
|||
print("=========================================")
|
||||
|
||||
user_prediction_thought = await cls.think_user_prediction(cache, inp)
|
||||
user_prediction_thought_revision = await cls.revise_user_prediction_thought(cache, user_prediction_thought, inp)
|
||||
user_prediction_thought_revision = await cls.revise_user_prediction_thought(
|
||||
cache, user_prediction_thought, inp
|
||||
)
|
||||
|
||||
print("=========================================")
|
||||
print("Finished User Prediction")
|
||||
|
|
@ -325,8 +418,6 @@ class BloomChain:
|
|||
yield "❀"
|
||||
|
||||
|
||||
|
||||
|
||||
class Streamable:
|
||||
"A async iterator wrapper for langchain streams that saves on completion via callback"
|
||||
|
||||
|
|
@ -335,10 +426,10 @@ class Streamable:
|
|||
self.callback = callback
|
||||
# self.content: List[Awaitable[BaseMessage]] = []
|
||||
self.content = ""
|
||||
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
|
||||
async def __anext__(self):
|
||||
try:
|
||||
data = await self.iterator.__anext__()
|
||||
|
|
@ -355,12 +446,13 @@ class Streamable:
|
|||
return self.message
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
async def __call__(self):
|
||||
async for _ in self:
|
||||
pass
|
||||
return self.content
|
||||
|
||||
|
||||
def unpack_messages(messages):
|
||||
unpacked = ""
|
||||
for message in messages:
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import StreamingResponse, JSONResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Dict
|
||||
import asyncio
|
||||
|
||||
# Local
|
||||
from chain import BloomChain
|
||||
from mediator import SupabaseMediator
|
||||
from cache import Conversation
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import sentry_sdk
|
||||
|
||||
load_dotenv()
|
||||
|
||||
rate = 0.2 if os.getenv("SENTRY_ENVIRONMENT") == "production" else 1.0
|
||||
sentry_sdk.init(
|
||||
dsn=os.environ["SENTRY_DSN"], traces_sample_rate=rate, profiles_sample_rate=rate
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
MEDIATOR = SupabaseMediator()
|
||||
LOCK = asyncio.Lock()
|
||||
|
||||
|
||||
### User Routes ###
|
||||
class User(BaseModel):
|
||||
user_id: str
|
||||
metadata: Optional[Dict]
|
||||
|
||||
|
||||
@app.get("/users")
|
||||
async def get_users():
|
||||
# TODO update to return a list of ID's
|
||||
pass
|
||||
|
||||
|
||||
@app.get("/users/{user_id}")
|
||||
async def get_user(user_id: str):
|
||||
# TODO: return user metadata
|
||||
pass
|
||||
|
||||
|
||||
### Session Meta Routes ###
|
||||
class UnknownSession(BaseModel):
|
||||
location_id: str = "default"
|
||||
|
||||
|
||||
class SessionMeta(BaseModel):
|
||||
location_id: str = "default"
|
||||
metadata: Optional[Dict] = None
|
||||
|
||||
|
||||
@app.get("/users/{user_id}/sessions")
|
||||
async def get_sessions(user_id, inp: UnknownSession):
|
||||
"""Return session ids and metadata associated with a user and location"""
|
||||
print(user_id, inp)
|
||||
# TODO update to return a list of ID's
|
||||
async with LOCK:
|
||||
data: str = MEDIATOR.get_sessions(user_id, inp.location_id)
|
||||
print(data)
|
||||
return JSONResponse(status_code=200, content=data)
|
||||
|
||||
|
||||
@app.post("/users/{user_id}/sessions")
|
||||
async def add_session(user_id, inp: SessionMeta):
|
||||
async with LOCK:
|
||||
data = MEDIATOR.add_session(user_id, inp.location_id, inp.metadata)
|
||||
return JSONResponse(status_code=200, content=data)
|
||||
|
||||
|
||||
@app.get("/users/{user_id}/sessions/{session_id}")
|
||||
async def get_session(user_id, session_id):
|
||||
"""Return session metadata"""
|
||||
async with LOCK:
|
||||
data: str = MEDIATOR.get_session(session_id)
|
||||
return JSONResponse(status_code=200, content=data)
|
||||
|
||||
|
||||
@app.put("/users/{user_id}/sessions/{session_id}")
|
||||
async def update_session(user_id, session_id, inp: SessionMeta):
|
||||
async with LOCK:
|
||||
MEDIATOR.update_session(session_id, inp.metadata)
|
||||
return JSONResponse(status_code=200, content={"message": "OK"})
|
||||
|
||||
|
||||
@app.delete("/users/{user_id}/sessions/{session_id}")
|
||||
async def delete_session(user_id, session_id):
|
||||
"""Delete a specific session"""
|
||||
async with LOCK:
|
||||
MEDIATOR.delete_session(session_id)
|
||||
return JSONResponse(status_code=200, content={"message": "OK"})
|
||||
|
||||
|
||||
### Session Message Routes ###
|
||||
class Message(BaseModel):
|
||||
message: str
|
||||
message_type: str
|
||||
|
||||
|
||||
@app.get("/users/{user_id}/sessions/{session_id}/messages")
|
||||
async def get_messages(user_id, session_id):
|
||||
"""Return messages associated with a session"""
|
||||
async with LOCK:
|
||||
data: str = MEDIATOR.get_messages(session_id)
|
||||
return JSONResponse(status_code=200, content=data)
|
||||
|
||||
|
||||
@app.post("/users/{user_id}/sessions/{session_id}/messages")
|
||||
async def add_message(user_id, session_id, inp: Message):
|
||||
"""Add a message to a session"""
|
||||
async with LOCK:
|
||||
MEDIATOR.add_message(session_id, inp.message_type, inp.message)
|
||||
return JSONResponse(status_code=200, content={"message": "OK"})
|
||||
|
||||
|
||||
class ChatInput(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
@app.post("/users/{user_id}/sessions/{session_id}/chat")
|
||||
async def voe(user_id, session_id, inp: ChatInput):
|
||||
async with LOCK:
|
||||
session = Conversation(MEDIATOR, user_id, session_id)
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
thought, response = await BloomChain.chat(session, inp.message)
|
||||
return {"thought": thought, "response": response}
|
||||
|
||||
|
||||
# @app.post("/stream")
|
||||
# async def stream(inp: SessionInput):
|
||||
# async with LOCK:
|
||||
# session = Conversation(MEDIATOR, user_id=inp.user_id, session_id=inp.session_id)
|
||||
# if session is None:
|
||||
# raise HTTPException(status_code=404, detail="Item not found")
|
||||
# return StreamingResponse(BloomChain.stream(session, inp.message))
|
||||
|
||||
|
||||
### API Fundamentals
|
||||
# @app.post('/tenant/new')
|
||||
# @app.post('application/new')
|
||||
# @app.get('/tenant')
|
||||
|
||||
|
||||
## Honcho Utilities
|
||||
# @app.get('/theoryofmind')
|
||||
# @app.get('/voe')
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
# from langchain.memory import PostgresChatMessageHistory
|
||||
from langchain.schema import Document
|
||||
from langchain.schema.messages import BaseMessage, _message_to_dict, messages_from_dict
|
||||
from langchain.vectorstores import SupabaseVectorStore
|
||||
from langchain.embeddings.base import Embeddings
|
||||
from langchain.embeddings.openai import OpenAIEmbeddings
|
||||
import uuid
|
||||
import sentry_sdk
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Supabase for Postgres Management
|
||||
from supabase.client import create_client, Client
|
||||
from typing import List, Dict
|
||||
import json
|
||||
|
||||
from abcs import Mediator
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class SupabaseMediator(Mediator):
|
||||
@sentry_sdk.trace
|
||||
def __init__(self):
|
||||
self.supabase: Client = create_client(
|
||||
os.environ["SUPABASE_URL"], os.environ["SUPABASE_KEY"]
|
||||
)
|
||||
self.memory_table = os.environ["MEMORY_TABLE"]
|
||||
self.session_table = os.environ["SESSION_TABLE"]
|
||||
self.match_function = os.environ["MATCH_FUNCTION"]
|
||||
|
||||
embeddings = OpenAIEmbeddings(
|
||||
deployment=os.environ["OPENAI_API_EMBEDDING_NAME"],
|
||||
model="text-embedding-ada-002",
|
||||
openai_api_base=os.environ["OPENAI_API_BASE"],
|
||||
openai_api_type=os.environ["OPENAI_API_TYPE"],
|
||||
)
|
||||
self.vector_table = SupabaseVectorStore(
|
||||
embedding=embeddings,
|
||||
client=self.supabase,
|
||||
table_name=os.environ["VECTOR_TABLE"],
|
||||
query_name=self.match_function,
|
||||
)
|
||||
|
||||
# self.supabase.table(self.session_table).insert({"id": session_id, "user_id": user_id, "location_id": location_id}).execute()
|
||||
|
||||
@sentry_sdk.trace
|
||||
def get_sessions(self, user_id: str, location_id: str | None):
|
||||
try:
|
||||
data = (
|
||||
self.supabase.table(self.session_table)
|
||||
.select("*")
|
||||
.eq("user_id", user_id)
|
||||
)
|
||||
print(location_id)
|
||||
data = (
|
||||
data.eq("location_id", location_id) if location_id is not None else data
|
||||
)
|
||||
response = (
|
||||
data.eq("isActive", True).order("created_at", desc=True).execute()
|
||||
)
|
||||
|
||||
print("response", response)
|
||||
|
||||
if response is not None:
|
||||
return response.data
|
||||
return None
|
||||
except Exception as e:
|
||||
print("========================================")
|
||||
print(e)
|
||||
print("========================================")
|
||||
return None
|
||||
|
||||
@sentry_sdk.trace
|
||||
def get_session(self, session_id):
|
||||
response = (
|
||||
self.supabase.table(self.session_table)
|
||||
.select("*")
|
||||
.eq("id", session_id)
|
||||
.eq("is_active", True)
|
||||
.single()
|
||||
.execute()
|
||||
)
|
||||
if response:
|
||||
return response.data
|
||||
return None
|
||||
|
||||
@sentry_sdk.trace
|
||||
def add_session(self, user_id: str, location_id: str, metadata: Dict) -> Dict:
|
||||
session_id = str(uuid.uuid4())
|
||||
payload = {
|
||||
"id": session_id,
|
||||
"user_id": user_id,
|
||||
"location_id": location_id,
|
||||
"metadata": metadata,
|
||||
}
|
||||
representation = self.supabase.table(self.session_table).insert(payload, returning="representation").execute() # type: ignore
|
||||
print("========================================")
|
||||
print(representation)
|
||||
print("========================================")
|
||||
return representation.data[0]
|
||||
|
||||
@sentry_sdk.trace
|
||||
def update_session(self, session_id: str, metadata: Dict) -> None:
|
||||
cur = (
|
||||
self.supabase.table(self.session_table)
|
||||
.select("metadata")
|
||||
.eq("id", session_id)
|
||||
.single()
|
||||
.execute()
|
||||
)
|
||||
if cur.data["metadata"] is not None:
|
||||
new_metadata = cur.data["metadata"].copy()
|
||||
new_metadata.update(metadata)
|
||||
else:
|
||||
new_metadata = metadata
|
||||
self.supabase.table(self.session_table).update({"metadata": new_metadata}, returning="representation").eq("id", session_id).execute() # type: ignore
|
||||
|
||||
@sentry_sdk.trace
|
||||
def delete_session(self, session_id: str) -> None:
|
||||
self.supabase.table(self.session_table).update({"isActive": False}).eq(
|
||||
"id", session_id
|
||||
).execute()
|
||||
|
||||
# Session Helper Methods
|
||||
|
||||
@sentry_sdk.trace
|
||||
def _cleanup_sessions(self, session_ids: List[str]) -> None:
|
||||
for session_id in session_ids:
|
||||
self.supabase.table(self.session_table).update({"isActive": False}).eq(
|
||||
"id", session_id
|
||||
).execute()
|
||||
|
||||
# Session Usage Read & Write
|
||||
|
||||
@sentry_sdk.trace
|
||||
def get_messages(self, session_id: str, type: str | None = None) -> List[BaseMessage]: # type: ignore
|
||||
response = (
|
||||
self.supabase.table(self.memory_table)
|
||||
.select("*")
|
||||
.eq("session_id", session_id)
|
||||
)
|
||||
response = (
|
||||
(response if type is None else response.eq("type", type))
|
||||
.order("created_at")
|
||||
.execute()
|
||||
)
|
||||
print(response)
|
||||
return response.data
|
||||
|
||||
@sentry_sdk.trace
|
||||
def add_message(self, session_id: str, message_type: str, content: str) -> None:
|
||||
payload = {
|
||||
"session_id": session_id,
|
||||
"type": message_type,
|
||||
"content": content,
|
||||
}
|
||||
self.supabase.table(self.memory_table).insert(payload).execute()
|
||||
File diff suppressed because it is too large
Load Diff
39
cache.py
39
cache.py
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
Below is an implementation of a basic LRUcache that utilizes the built
|
||||
in OrderedDict data structure.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
from mediator import SupabaseMediator
|
||||
import uuid
|
||||
from typing import List
|
||||
from langchain.schema import BaseMessage, Document
|
||||
from pydantic import BaseModel
|
||||
import sentry_sdk
|
||||
|
||||
class Conversation:
|
||||
"Wrapper Class for storing contexts between channels. Using an object to pass by reference avoid additional cache hits"
|
||||
@sentry_sdk.trace
|
||||
def __init__(self, mediator: SupabaseMediator, user_id: str, conversation_id: str = str(uuid.uuid4()), location_id: str = "web"):
|
||||
self.mediator: SupabaseMediator = mediator
|
||||
self.user_id: str = user_id
|
||||
self.conversation_id: str = conversation_id
|
||||
self.location_id: str = location_id
|
||||
|
||||
@sentry_sdk.trace
|
||||
def add_message(self, message_type: str, message: BaseMessage,) -> None:
|
||||
self.mediator.add_message(self.conversation_id, self.user_id, message_type, message)
|
||||
|
||||
@sentry_sdk.trace
|
||||
def messages(self, message_type: str) -> List[BaseMessage]:
|
||||
return self.mediator.messages(self.conversation_id, self.user_id, message_type)
|
||||
|
||||
# vector DB fn
|
||||
@sentry_sdk.trace
|
||||
def add_texts(self, texts: List[str]) -> None:
|
||||
metadatas = [{"conversation_id": self.conversation_id, "user_id": self.user_id} for _ in range(len(texts))]
|
||||
self.mediator.vector_table.add_texts(texts, metadatas)
|
||||
|
||||
# vector DB fn
|
||||
@sentry_sdk.trace
|
||||
def similarity_search(self, query: str, match_count: int = 5) -> List[Document]:
|
||||
return self.mediator.vector_table.similarity_search(query=query, k=match_count, filter={"user_id": self.user_id})
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
from typing import List
|
||||
|
||||
from langchain.prompts import ChatPromptTemplate
|
||||
from langchain.schema import AIMessage, HumanMessage, SystemMessage
|
||||
from langchain_community.chat_models.fake import FakeListChatModel
|
||||
|
||||
from honcho.client import HonchoClient
|
||||
# from client import HonchoClient
|
||||
|
||||
honcho = HonchoClient(base_url="http://localhost:8000")
|
||||
|
||||
responses = ["Fake LLM Response :)"]
|
||||
llm = FakeListChatModel(responses=responses)
|
||||
system = SystemMessage(content="You are world class technical documentation writer. Be as concise as possible")
|
||||
|
||||
user = "CLI-Test"
|
||||
session = honcho.create_session(user_id=user)
|
||||
session_id = session["id"]
|
||||
|
||||
def langchain_message_converter(messages: List):
|
||||
new_messages = []
|
||||
for message in messages:
|
||||
if message["is_user"]:
|
||||
new_messages.append(HumanMessage(content=message["content"]))
|
||||
else:
|
||||
new_messages.append(AIMessage(content=message["content"]))
|
||||
return new_messages
|
||||
|
||||
def chat():
|
||||
while True:
|
||||
user_input = input("User: ")
|
||||
if user_input == "exit":
|
||||
honcho.delete_session(user, session_id)
|
||||
break
|
||||
user_message = HumanMessage(content=user_input)
|
||||
history = honcho.get_messages_for_session(user, session_id)
|
||||
langchain_history = langchain_message_converter(history)
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
system,
|
||||
*langchain_history,
|
||||
user_message
|
||||
])
|
||||
chain = prompt | llm
|
||||
response = chain.invoke({})
|
||||
print(type(response))
|
||||
print(f"AI: {response.content}")
|
||||
honcho.create_message_for_session(user, session_id, is_user=True, content=user_input)
|
||||
honcho.create_message_for_session(user, session_id, is_user=False, content=response.content)
|
||||
|
||||
chat()
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,16 @@
|
|||
[tool.poetry]
|
||||
name = "honcho-cli-example"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
langchain = "^0.1.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
[tool.poetry]
|
||||
name = "honcho-discord-example"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>"]
|
||||
readme = "README.md"
|
||||
packages = [{include = "honcho_discord_example"}]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
langchain = "^0.1.0"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
55
main.py
55
main.py
|
|
@ -1,55 +0,0 @@
|
|||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
import asyncio
|
||||
|
||||
# Local
|
||||
from chain import BloomChain
|
||||
from mediator import SupabaseMediator
|
||||
from cache import Conversation
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import sentry_sdk
|
||||
load_dotenv()
|
||||
|
||||
rate = 0.2 if os.getenv("SENTRY_ENVIRONMENT") == "production" else 1.0
|
||||
sentry_sdk.init(
|
||||
dsn=os.environ['SENTRY_DSN'],
|
||||
traces_sample_rate=rate,
|
||||
profiles_sample_rate=rate
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
MEDIATOR = SupabaseMediator()
|
||||
LOCK = asyncio.Lock()
|
||||
|
||||
class ConversationInput(BaseModel):
|
||||
user_id: str
|
||||
conversation_id: str
|
||||
message: str
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "Hello World"}
|
||||
|
||||
@app.post("/chat")
|
||||
async def voe(inp: ConversationInput):
|
||||
async with LOCK:
|
||||
conversation = Conversation(MEDIATOR, user_id=inp.user_id, conversation_id=inp.conversation_id)
|
||||
if conversation is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
thought, response = await BloomChain.chat(conversation, inp.message)
|
||||
return {
|
||||
"thought": thought,
|
||||
"response": response
|
||||
}
|
||||
|
||||
@app.post("/stream")
|
||||
async def stream(inp: ConversationInput):
|
||||
async with LOCK:
|
||||
conversation = Conversation(MEDIATOR, user_id=inp.user_id, conversation_id=inp.conversation_id)
|
||||
if conversation is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return StreamingResponse(BloomChain.stream(conversation, inp.message))
|
||||
76
mediator.py
76
mediator.py
|
|
@ -1,76 +0,0 @@
|
|||
from langchain.memory import PostgresChatMessageHistory
|
||||
from langchain.schema import Document
|
||||
from langchain.schema.messages import BaseMessage, _message_to_dict, messages_from_dict
|
||||
from langchain.vectorstores import SupabaseVectorStore
|
||||
from langchain.embeddings.base import Embeddings
|
||||
from langchain.embeddings import AzureOpenAIEmbeddings
|
||||
import uuid
|
||||
import sentry_sdk
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
# Supabase for Postgres Management
|
||||
from supabase.client import create_client, Client
|
||||
from supabase.lib.client_options import ClientOptions
|
||||
from typing import List
|
||||
import json
|
||||
|
||||
load_dotenv()
|
||||
|
||||
class SupabaseMediator:
|
||||
|
||||
@sentry_sdk.trace
|
||||
def __init__(self):
|
||||
timeout_client_options = ClientOptions(postgrest_client_timeout=60)
|
||||
self.supabase: Client = create_client(os.environ['SUPABASE_URL'], os.environ['SUPABASE_KEY'], timeout_client_options)
|
||||
# self.supabase: Client = create_client(os.environ['SUPABASE_URL'], os.environ['SUPABASE_KEY'])
|
||||
self.memory_table = os.environ["MEMORY_TABLE"]
|
||||
self.conversation_table = os.environ["CONVERSATION_TABLE"]
|
||||
self.match_function = os.environ["MATCH_FUNCTION"]
|
||||
|
||||
embeddings = AzureOpenAIEmbeddings(
|
||||
deployment=os.environ["OPENAI_API_EMBEDDING_NAME"],
|
||||
model="text-embedding-ada-002",
|
||||
)
|
||||
self.vector_table = SupabaseVectorStore(
|
||||
embedding=embeddings,
|
||||
client=self.supabase,
|
||||
table_name=os.environ["VECTOR_TABLE"],
|
||||
query_name=self.match_function
|
||||
)
|
||||
# # seed the vector store with facts about bloom
|
||||
# seed_docs = [
|
||||
# Document(page_content="Bloom is your learning companion"),
|
||||
# Document(page_content="Bloom can be used for learning just about anything! It's your ultimate school assistant."),
|
||||
# ]
|
||||
|
||||
# self.vector_table.add_documents(seed_docs)
|
||||
|
||||
@sentry_sdk.trace
|
||||
def messages(self, session_id: str, user_id: str, message_type: str) -> List[BaseMessage]: # type: ignore
|
||||
response = self.supabase.table(self.memory_table).select("message").eq("session_id", session_id).eq("user_id", user_id).eq("message_type", message_type).order("id", desc=True).limit(10).execute()
|
||||
items = [record["message"] for record in response.data]
|
||||
messages = messages_from_dict(items)
|
||||
return messages[::-1]
|
||||
|
||||
@sentry_sdk.trace
|
||||
def add_message(self, session_id: str, user_id: str, message_type: str, message: BaseMessage) -> None:
|
||||
self.supabase.table(self.memory_table).insert({"session_id": session_id, "user_id": user_id, "message_type": message_type, "message": _message_to_dict(message)}).execute()
|
||||
|
||||
@sentry_sdk.trace
|
||||
def conversations(self, location_id: str, user_id: str) -> str | None:
|
||||
response = self.supabase.table(self.conversation_table).select("id").eq("location_id", location_id).eq("user_id", user_id).eq("isActive", True).maybe_single().execute()
|
||||
if response:
|
||||
conversation_id = response.data["id"]
|
||||
return conversation_id
|
||||
return None
|
||||
|
||||
@sentry_sdk.trace
|
||||
def add_conversation(self, location_id: str, user_id: str) -> str:
|
||||
conversation_id = str(uuid.uuid4())
|
||||
self.supabase.table(self.conversation_table).insert({"id": conversation_id, "user_id": user_id, "location_id": location_id}).execute()
|
||||
return conversation_id
|
||||
|
||||
@sentry_sdk.trace
|
||||
def delete_conversation(self, conversation_id: str) -> None:
|
||||
self.supabase.table(self.conversation_table).update({"isActive": False}).eq("id", conversation_id).execute()
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from .client import Client
|
||||
from .cache import LRUCache
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
class LRUCache:
|
||||
"""
|
||||
An implementation of a basic LRUcache that utilizes the built
|
||||
in OrderedDict data structure.
|
||||
"""
|
||||
def __init__(self, capacity: int):
|
||||
self.capacity = capacity
|
||||
self.cache = OrderedDict()
|
||||
|
||||
def get(self, key: str):
|
||||
"""Get a value from the cache"""
|
||||
if key not in self.cache:
|
||||
return None
|
||||
|
||||
# Move the accessed key to the end to indicate it was recently used
|
||||
self.cache.move_to_end(key)
|
||||
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)
|
||||
else:
|
||||
if len(self.cache) >= self.capacity:
|
||||
# If the cache is full, remove the least recently used key-value pair (the first item in the OrderedDict)
|
||||
self.cache.popitem(last=False)
|
||||
|
||||
# Add or update the key-value pair at the end of the OrderedDict
|
||||
self.cache[key] = value
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
from typing import Dict
|
||||
import requests
|
||||
|
||||
class Client:
|
||||
def __init__(self, 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
|
||||
}
|
||||
url = f"{self.base_url}/users/{user_id}/sessions"
|
||||
response = requests.post(url, json=data)
|
||||
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
|
||||
}
|
||||
url = f"{self.base_url}/users/{user_id}/sessions/{session_id}"
|
||||
response = requests.put(url, json=data)
|
||||
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
|
||||
}
|
||||
url = f"{self.base_url}/users/{user_id}/sessions/{session_id}/messages"
|
||||
response = requests.post(url, json=data)
|
||||
return response.json()
|
||||
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2023.11.17"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
|
||||
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
|
||||
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.6"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.31.0"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<4"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<3"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.1.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
|
||||
{file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "a5780ef8e06df616beb6eb67292099db49b8fe658fcbf22940e5e1af96a7c14e"
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
[tool.poetry]
|
||||
name = "honcho-ai"
|
||||
version = "0.0.1"
|
||||
description = "Python Client SDK for Honcho"
|
||||
authors = ["Plastic Labs <hello@plasticlabs.ai>"]
|
||||
license = "AGPL-3.0"
|
||||
readme = "README.md"
|
||||
packages = [{include = "honcho"}]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
requests = "^2.31.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
Loading…
Reference in New Issue