├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ ├── docs.yml │ ├── frontend_release.yml │ ├── pypi-release.yml │ └── run_pytest.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── API │ ├── index.md │ ├── payload_formats.md │ └── user_and_tree_managers.md ├── Advanced │ ├── advanced_tool_construction.md │ ├── custom_objects.md │ ├── environment.md │ ├── index.md │ ├── local_models.md │ └── technical_overview.md ├── Examples │ ├── data_analysis.md │ ├── email.md │ ├── fantasy_adventure.md │ ├── index.md │ ├── old_data_analysis.md │ ├── query_weaviate.md │ └── sentiment_analysis.md ├── Reference │ ├── Client.md │ ├── Managers.md │ ├── Objects.md │ ├── PayloadTypes.md │ ├── Preprocessor.md │ ├── Settings.md │ ├── Tree.md │ └── Util.md ├── advanced_usage.md ├── basic.md ├── creating_tools.md ├── img │ ├── diabetes-regression-example.png │ ├── regression-example.png │ └── technical_overview_1.png ├── index.md └── setting_up.md ├── elysia ├── __init__.py ├── __metadata__.py ├── api │ ├── __init__.py │ ├── api_types.py │ ├── app.py │ ├── cli.py │ ├── core │ │ ├── __init__.py │ │ └── log.py │ ├── custom_tools.py │ ├── dependencies │ │ ├── __init__.py │ │ └── common.py │ ├── middleware │ │ ├── __init__.py │ │ └── error_handlers.py │ ├── routes │ │ ├── __init__.py │ │ ├── collections.py │ │ ├── db.py │ │ ├── feedback.py │ │ ├── init.py │ │ ├── processor.py │ │ ├── query.py │ │ ├── tools.py │ │ ├── tree_config.py │ │ ├── user_config.py │ │ └── utils.py │ ├── services │ │ ├── __init__.py │ │ ├── tree.py │ │ └── user.py │ ├── static │ │ ├── 404.html │ │ ├── _next │ │ │ └── static │ │ │ │ ├── chunks │ │ │ │ ├── 0e762574-c759a1561c192a17.js │ │ │ │ ├── 117-25e58301f848c60f.js │ │ │ │ ├── 1a258343-150c78f4297d2358.js │ │ │ │ ├── 30a37ab2-322b0b940673459b.js │ │ │ │ ├── 349-1022b66d793b606a.js │ │ │ │ ├── 385cb88d-8bfdadb8caecc2fb.js │ │ │ │ ├── 455.7a2f31b70b175710.js │ │ │ │ ├── 479ba886-f85ef559edbf188b.js │ │ │ │ ├── 52ab8b6c-668757d47a6e6fc6.js │ │ │ │ ├── 53c13509-fcf4cae42fb19e2e.js │ │ │ │ ├── 59650de3-4892ddf82b39e2a7.js │ │ │ │ ├── 5e22fd23-dcf8510321a066a0.js │ │ │ │ ├── 656-1c41fa57e3c53c77.js │ │ │ │ ├── 695-196e1d8b26f70535.js │ │ │ │ ├── 748.3b9d198cc4056057.js │ │ │ │ ├── 795d4814-0bb2b0233fd884e6.js │ │ │ │ ├── 870-b162a2344758d531.js │ │ │ │ ├── 8e1d74a4-bd573e72e177a84c.js │ │ │ │ ├── 94730671-8100cd70fbbb3758.js │ │ │ │ ├── 9c4e2130-a71a68ade6eea068.js │ │ │ │ ├── app │ │ │ │ │ ├── _not-found │ │ │ │ │ │ └── page-a7aa6e10be2a8ab3.js │ │ │ │ │ ├── layout-2af1a2254fac3002.js │ │ │ │ │ └── page-6990bd818590806c.js │ │ │ │ ├── b536a0f1.101823f740c1a8e5.js │ │ │ │ ├── c916193b-2488e101c53116b7.js │ │ │ │ ├── e34aaff9-0dee46ea085fd2ca.js │ │ │ │ ├── eec3d76d-069cf1d79aaf4965.js │ │ │ │ ├── f7333993-a8b5748c01dd26c0.js │ │ │ │ ├── f8025e75-f62f273c531a4aaa.js │ │ │ │ ├── f97e080b-aae770272181a2bd.js │ │ │ │ ├── fca4dd8b-1b5ad2d023949d77.js │ │ │ │ ├── fd9d1056-46f8d054f716b978.js │ │ │ │ ├── framework-cf2d8f6f8d843863.js │ │ │ │ ├── main-543044a68b369561.js │ │ │ │ ├── main-app-edae924f4ed22c2f.js │ │ │ │ ├── pages │ │ │ │ │ ├── _app-15e2daefa259f0b5.js │ │ │ │ │ └── _error-28b803cb2479b966.js │ │ │ │ ├── polyfills-42372ed130431b0a.js │ │ │ │ └── webpack-bcddd50ab1ee801f.js │ │ │ │ ├── css │ │ │ │ ├── 9c6f759383f5db47.css │ │ │ │ ├── be9a1ea42b131522.css │ │ │ │ └── e70d82330d5eef69.css │ │ │ │ ├── i-07ZcKE-6kg2so1Pq3CQ │ │ │ │ ├── _buildManifest.js │ │ │ │ └── _ssgManifest.js │ │ │ │ └── media │ │ │ │ ├── 36966cca54120369-s.p.woff2 │ │ │ │ ├── 438aa629764e75f3-s.woff2 │ │ │ │ ├── 4c9affa5bc8f420e-s.p.woff2 │ │ │ │ ├── 51251f8b9793cdb3-s.woff2 │ │ │ │ ├── 875ae681bfde4580-s.woff2 │ │ │ │ ├── b7387a63dd068245-s.woff2 │ │ │ │ ├── cc978ac5ee68c2b6-s.woff2 │ │ │ │ ├── e1aab0933260df4d-s.woff2 │ │ │ │ └── e857b654a2caa584-s.woff2 │ │ ├── icon.svg │ │ ├── index.html │ │ ├── index.txt │ │ ├── logo.svg │ │ └── weaviate-logo.svg │ ├── user_configs │ │ └── __init__.py │ └── utils │ │ ├── __init__.py │ │ ├── config.py │ │ ├── default_payloads.py │ │ ├── encryption.py │ │ ├── feedback.py │ │ ├── models.py │ │ ├── ner.py │ │ ├── resources.py │ │ └── websocket.py ├── config.py ├── objects.py ├── preprocessing │ ├── __init__.py │ ├── collection.py │ └── prompt_templates.py ├── tools │ ├── __init__.py │ ├── postprocessing │ │ ├── __init__.py │ │ ├── prompt_templates.py │ │ └── summarise_items.py │ ├── retrieval │ │ ├── __init__.py │ │ ├── aggregate.py │ │ ├── chunk.py │ │ ├── objects.py │ │ ├── prompt_templates.py │ │ ├── query.py │ │ └── util.py │ ├── text │ │ ├── __init__.py │ │ ├── objects.py │ │ ├── prompt_templates.py │ │ └── text.py │ └── visualisation │ │ ├── __init__.py │ │ ├── linear_regression.py │ │ ├── objects.py │ │ ├── prompt_templates.py │ │ ├── util.py │ │ └── visualise.py ├── tree │ ├── __init__.py │ ├── objects.py │ ├── prompt_templates.py │ ├── tree.py │ └── util.py └── util │ ├── __init__.py │ ├── async_util.py │ ├── client.py │ ├── collection.py │ ├── dummy_adapter.py │ ├── elysia_chain_of_thought.py │ ├── objects.py │ ├── parsing.py │ ├── retrieve_feedback.py │ └── return_types.py ├── img ├── architecture.png ├── banner.gif ├── config.png ├── elysia.gif └── thumbnail.png ├── mkdocs.yml ├── pyproject.toml └── tests ├── __init__.py ├── conftest.py ├── no_reqs ├── api │ ├── conftest.py │ ├── test_api_keys_nr.py │ ├── test_change_tools_nr.py │ ├── test_config_nr.py │ ├── test_init_nr.py │ ├── test_managers_nr.py │ └── test_query_nr.py └── general │ ├── conftest.py │ ├── test_chunker_nr.py │ ├── test_client_local_nr.py │ ├── test_objects_nr.py │ ├── test_save_load_nr.py │ ├── test_settings_nr.py │ ├── test_tools_nr.py │ ├── test_tree_config_nr.py │ └── test_tree_nr.py └── requires_env ├── README.md ├── api ├── conftest.py ├── test_api_keys.py ├── test_collections.py ├── test_config.py ├── test_db.py ├── test_feedback.py ├── test_managers.py ├── test_processor.py └── test_utils.py ├── conftest.py ├── general ├── conftest.py ├── test_chunking.py ├── test_client.py ├── test_document_retrieval.py ├── test_save_load.py └── test_settings.py └── llm ├── conftest.py ├── deepeval_setup.py ├── test_advanced_save_load.py ├── test_complex_prompts.py ├── test_customisations.py ├── test_docs_examples.py ├── test_general.py ├── test_generic_prompts.py ├── test_local.py ├── test_retrieval_accuracy.py ├── test_self_healing.py └── test_tree_methods.py /.env.example: -------------------------------------------------------------------------------- 1 | # WCD_URL= 2 | # WCD_API_KEY= 3 | 4 | #WEAVIATE_IS_LOCAL 5 | #LOCAL_WEAVIATE_PORT 6 | #LOCAL_WEAVIATE_GRPC_PORT 7 | 8 | # OPENROUTER_API_KEY= 9 | 10 | # ANTHROPIC_API_KEY= 11 | # ANYSCALE_API_KEY= 12 | # AWS_ACCESS_KEY= 13 | # AWS_SECRET_KEY= 14 | # COHERE_API_KEY= 15 | # DATABRICKS_TOKEN= 16 | # FRIENDLI_TOKEN= 17 | # VERTEX_API_KEY= 18 | # STUDIO_API_KEY= 19 | # HUGGINGFACE_API_KEY= 20 | # JINAAI_API_KEY= 21 | # MISTRAL_API_KEY= 22 | # NVIDIA_API_KEY= 23 | # OPENAI_API_KEY= 24 | # AZURE_API_KEY= 25 | # VOYAGE_API_KEY= 26 | # XAI_API_KEY= 27 | 28 | # BASE_MODEL=gemini-2.0-flash-001 29 | # COMPLEX_MODEL=gemini-2.0-flash-001 30 | # BASE_PROVIDER=openrouter/google 31 | # COMPLEX_PROVIDER=openrouter/google -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug in the project 3 | title: "[Bug] " 4 | labels: bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## 🐛 Bug Report 10 | Please fill out all required fields to help us diagnose and fix the issue. 11 | 12 | - type: input 13 | id: environment 14 | attributes: 15 | label: "Elysia version" 16 | description: "Tell us your Elysia version. (You can find this out via `pip show elysia-ai` in your environment that you installed Elysia in.)" 17 | validations: 18 | required: true 19 | 20 | - type: input 21 | id: installation_method 22 | attributes: 23 | label: "Installation method" 24 | description: "How did you install Elysia? (e.g. pip, uv, conda, from source, etc.)" 25 | placeholder: "pip install elysia-ai" 26 | validations: 27 | required: true 28 | 29 | - type: dropdown 30 | id: app_or_python 31 | attributes: 32 | label: "Is the issue occurring in the Elysia package or the Elysia web app?" 33 | description: "Please note that if your bug looks like a frontend problem (e.g. items not displaying, etc.), you might want to submit a bug report to the Elysia frontend repository instead: https://github.com/weaviate/elysia-frontend. However, if you are getting _errors_ in the app, then it is likely the backend and suited for here." 34 | options: 35 | - "Python package (running Elysia in python)" 36 | - "Elysia web app (using the app via `elysia start`)" 37 | - "Both" 38 | 39 | - type: textarea 40 | id: description 41 | attributes: 42 | label: "What happened?" 43 | description: "Clearly describe the unexpected behavior. Attach a full error log if possible. In the web app, you may have an error in the terminal you ran `elysia start` from." 44 | placeholder: "Whenever I try to run a query, I get an error message..." 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | id: steps-to-reproduce 50 | attributes: 51 | label: "Steps to reproduce" 52 | description: "Please provide a code snippet if applicable, or what you were doing when the issue occurred. If it seems a problem with your data, attach a (dummy) sample of your data or your schema. In Weaviate, you can do `print(client.collections.get(\"\").config.get())`" 53 | 54 | - type: textarea 55 | id: additional_context 56 | attributes: 57 | label: "Additional context" 58 | description: "Tell us any additional context that might be helpful. E.g., are you running local weaviate, is it a local model (which one), any special considerations, etc." 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature or improvement 3 | title: "[Feature] " 4 | labels: enhancement 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## Feature Request 10 | Please fill out all required fields to help us understand and implement the feature. 11 | 12 | - type: textarea 13 | id: description 14 | attributes: 15 | label: "What feature would you like to see?" 16 | description: "Describe the feature clearly." 17 | validations: 18 | required: true 19 | 20 | - type: checkboxes 21 | id: contribute 22 | attributes: 23 | label: "Would you like to contribute?" 24 | options: 25 | - label: Yes, I'd like to help implement this. 26 | - label: No, I just want to request it. 27 | 28 | 29 | - type: textarea 30 | id: additional-info 31 | attributes: 32 | label: "Additional Context" 33 | description: "Any links, references, or extra details?" 34 | placeholder: "Example: This feature exists in XYZ tool." -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## 📝 Changes Description 5 | 6 | This PR contains the following changes: 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## ✅ Contributor Checklist 15 | 16 | 17 | - [ ] `no_reqs` tests are passing locally 18 | - [ ] `requires_env` tests are passing locally (optional - see [Testing guidelines](https://github.com/weaviate/elysia?tab=contributing-ov-file#testing)) 19 | - [ ] You agree to Weaviate's [Contributor License Agreement](https://weaviate.io/service/contributor-license-agreement) 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | on: 3 | push: 4 | branches: 5 | - dev 6 | - dev-v2 7 | - main 8 | 9 | permissions: 10 | contents: write 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: 3.x 19 | - uses: actions/cache@v4 20 | with: 21 | key: ${{ github.ref }} 22 | path: .cache 23 | - run: pip install 'mkdocs-material[imaging]' 24 | - run: pip install 'mkdocstrings[python]' 25 | - run: pip install pillow cairosvg 26 | - run: mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.github/workflows/pypi-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build-and-publish: 10 | name: Build and publish to PyPI 11 | runs-on: ubuntu-latest 12 | if: github.repository_owner == 'weaviate' 13 | permissions: 14 | id-token: write 15 | contents: write 16 | 17 | environment: 18 | name: pypi 19 | 20 | steps: 21 | - name: Check out repository 22 | uses: actions/checkout@v4 23 | with: 24 | persist-credentials: true 25 | fetch-depth: 1 26 | 27 | - name: Set up Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: "3.12" 31 | 32 | - name: Update version in pyproject.toml 33 | run: | 34 | TAG_VERSION="${GITHUB_REF_NAME#v}" 35 | echo "Setting version to $TAG_VERSION in pyproject.toml" 36 | # Use sed without the '' flag for Linux compatibility 37 | sed -i "/#replace_package_version_marker/{n;s/version *= *\"[^\"]*\"/version=\"$TAG_VERSION\"/;}" pyproject.toml 38 | sed -i "/# replace_package_version_marker/{n;s/__version__ *= *\"[^\"]*\"/__version__=\"$TAG_VERSION\"/;}" ./elysia/__metadata__.py 39 | 40 | - name: Install pypa/build 41 | run: >- 42 | python3 -m 43 | pip install 44 | build 45 | --user 46 | 47 | - name: Build a binary wheel and a source tarball 48 | run: python3 -m build 49 | 50 | - name: Store the distribution packages 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: python-package-distributions 54 | path: dist/ 55 | 56 | - name: Download all the dists 57 | uses: actions/download-artifact@v4 58 | with: 59 | name: python-package-distributions 60 | path: dist/ 61 | 62 | - name: Publish release distributions to PyPI 63 | uses: pypa/gh-action-pypi-publish@release/v1 64 | with: 65 | packages-dir: dist/ 66 | 67 | - name: Create test environment 68 | run: python -m venv test_before_pypi 69 | 70 | - name: Check the install 71 | run: | 72 | source test_before_pypi/bin/activate 73 | pip install dist/*.whl 74 | deactivate 75 | rm -r test_before_pypi 76 | 77 | - uses: stefanzweifel/git-auto-commit-action@v5 # auto commit changes to main 78 | with: 79 | commit_message: Update versions 80 | create_branch: true 81 | branch: release/${{ github.ref }} 82 | 83 | - name: Checkout main branch 84 | run: | 85 | git fetch origin 86 | git checkout main 87 | 88 | - name: Configure git user 89 | run: | 90 | git config --global user.email "actions@github.com" 91 | git config --global user.name "Github Actions" 92 | 93 | - name: Merge release branch into main 94 | run: | 95 | git merge --no-ff release/${{ github.ref }} 96 | 97 | - name: Push changes to main 98 | run: | 99 | git push origin main 100 | 101 | 102 | create-release: 103 | name: Create Release 104 | needs: build-and-publish 105 | runs-on: ubuntu-latest 106 | steps: 107 | - name: Checkout code 108 | uses: actions/checkout@master 109 | - name: Create Release 110 | id: create_release 111 | uses: actions/create-release@latest 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 114 | with: 115 | tag_name: ${{ github.ref }} 116 | release_name: ${{ github.ref }} 117 | draft: true 118 | prerelease: false -------------------------------------------------------------------------------- /.github/workflows/run_pytest.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Test via Pytest 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - dev 8 | - 'release/*' 9 | 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | checks: write 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | python-version: ["3.12"] 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | continue-on-error: true 34 | 35 | - name: Install Elysia 36 | run: | 37 | pip install ".[dev]" 38 | continue-on-error: true 39 | 40 | - name: Test with pytest 41 | id: pytest 42 | run: | 43 | pytest --ignore=tests/requires_env --tb=short -v --junitxml=pytest-results.xml 44 | continue-on-error: true 45 | 46 | - name: Publish Test Results 47 | uses: EnricoMi/publish-unit-test-result-action@v2 48 | if: (!cancelled()) 49 | with: 50 | files: | 51 | pytest-results.xml 52 | check_name: "Pytest Results" 53 | comment_title: "Unit Test Results" 54 | comment_mode: always 55 | 56 | - name: Fail job if tests failed 57 | if: steps.pytest.outcome == 'failure' 58 | run: exit 1 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Ignore all dotfiles except .gitignore and workflows 36 | .* 37 | !.gitignore 38 | !.github/ 39 | !.github/workflows/ 40 | !.github/workflows/** 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | cover/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | docsrc/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # IPython 89 | profile_default/ 90 | ipython_config.py 91 | 92 | # pyenv 93 | .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | # Python cache files 146 | __pycache__/ 147 | */**/__pycache__/ 148 | */__pycache__/ 149 | *.py[cod] 150 | *.pyo 151 | *.pyd 152 | 153 | # uv 154 | 155 | uv.lock 156 | 157 | # Frontend 158 | elysia-frontend 159 | elysia-frontend/ 160 | elysia-frontend/* 161 | elysia-frontend/.git/* 162 | 163 | # elysia/api/static/* 164 | 165 | # resources 166 | resources.txt 167 | 168 | # local user configs 169 | elysia/api/user_configs/* 170 | !elysia/api/user_configs/__init__.py 171 | 172 | # profiling 173 | *.prof 174 | *.austin 175 | *.echion 176 | 177 | # run 178 | rundocs.sh 179 | runpy.sh 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2023, Weaviate B.V. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft elysia 2 | graft tests 3 | graft docs 4 | 5 | include LICENSE 6 | include README.md 7 | 8 | 9 | global-exclude *.py[co] __pycache__ -------------------------------------------------------------------------------- /docs/API/index.md: -------------------------------------------------------------------------------- 1 | Within the Elysia package, the API endpoints are included but are specific to the [Elysia Frontend](https://github.com/weaviate/elysia-frontend). 2 | 3 | However, there are a range of functionalities included that can be useful for using Elysia in an app environment. -------------------------------------------------------------------------------- /docs/API/payload_formats.md: -------------------------------------------------------------------------------- 1 | 2 | Whenever a [`Result`](../Reference/Objects.md#elysia.objects.Result) object is _yielded_ from an Elysia tool or decision agent, two things happen: 3 | 4 | 1. Any objects and metadata in the `Result` are automatically added to Elysia's [Environment](../Advanced/environment.md) for future use in the decision tree. 5 | 2. The `.to_frontend()` method of the `Result` parses the objects and metadata to a frontend-acceptable format and are yielded outside of the tree (to send payloads to a connected frontend). 6 | 7 | Similarly, an [`Update`](../Reference/Objects.md#elysia.objects.Update) class also yields a payload outside of the tree, but does not add any objects to the environment. 8 | 9 | All payloads that are sent from the decision tree to the frontend have the same structure: 10 | ```python 11 | { 12 | "type": str, 13 | "id": str, 14 | "user_id": str, 15 | "conversation_id": str, 16 | "query_id": str, 17 | "payload": dict 18 | } 19 | ``` 20 | 21 | Where the `"payload"` dictionary always contains: 22 | 23 | ```python 24 | { 25 | "type": str, 26 | "metadata": dict 27 | "objects": list[dict], 28 | ... # additional fields 29 | } 30 | ``` 31 | where the additional fields are based on the `"type"` of the output. The dictionaries in the `list[dict]` of the `objects` is normally unique to each `"type"` that is returned, but will always include a `_REF_ID` field containing a unique identifier for its place in the Elysia environment. 32 | 33 | For example, any objects returned by the Elysia query tool will be mapped to specific fields that the frontend is 'aware' of. Items in the Weaviate collection that are returned are not known how to be displayed by the frontend as the fields are unique to the user's collection. So instead they are mapped to frontend-specific fields that are decided in advance by the [preprocessing step](../setting_up.md#preprocessing-collections) before they are returned outside of the tree. 34 | 35 | - -------------------------------------------------------------------------------- /docs/Advanced/index.md: -------------------------------------------------------------------------------- 1 | # Customising Elysia Overview 2 | 3 | If you haven't already, you should read the [basic usage guide](../basic.md) and the [advanced usage guide](../advanced_usage.md) (which details exactly how to specify custom agent descriptions and styles). 4 | 5 | There are many ways you can make Elysia into a completely custom app with its own goals and tools. This section will detail exactly how you can create tools, how to interact with the Elysia decision tree's objects and to create your own. 6 | 7 | - [Technical Overview](technical_overview.md) 8 | - [How to create your own tools](advanced_tool_construction.md) 9 | - [How to interact with the Elysia Environment](environment.md) 10 | - [How to create your own objects](custom_objects.md) 11 | -------------------------------------------------------------------------------- /docs/Advanced/technical_overview.md: -------------------------------------------------------------------------------- 1 | # Technical Overview 2 | 3 | Let's break down the Elysia decision tree, how exactly it runs and how objects persist over multiple iterations. 4 | 5 | We will consider a 'standard' Elysia set up as an example, where we have access to two tools: 'Query' and 'Aggregate', which both interact with custom data. There are two additional tools: 'Summarize' and 'Text Response', which both provide text outputs (with slightly different specifications). 6 | 7 | ![Decision Agent](../img/technical_overview_1.png){ align="center" width="75%" } 8 | 9 | ## Decision Agent 10 | 11 | The decision agent (which is run from the `base_model`) is responsible for choosing the tools to call/nodes for each decision step. The decision tree structure consists of 'branches', which are subcategories for tools, used to separate out different tools if there are many of them, and tools, which are actions to perform. Tools should add information to the Elysia _environment_. The environment is used within the decision agent as well as any tools so the process is aware of any retrieved or collected data. 12 | 13 | The inputs to the decision tree are: 14 | 15 | - The tree data ([see below](#tree-data)). 16 | - Tool descriptions. 17 | - Instruction for the branch (e.g. how to make the choice between the currently available tools). 18 | - Metadata on the tree, including how many loops through the tree has been made, as well as future tools that exist within a branch. 19 | 20 | The decision agent assigns the following outputs: 21 | 22 | - Tool to use. 23 | - Inputs to the tool if they exist. 24 | - Whether to end the tree after calling this tool (although this is conditional on the tool also allowing the process to end after its call). 25 | - A message update to the user. 26 | - Whether the task is impossible given all other information. 27 | 28 | 29 | ## Tree Data 30 | 31 | The tree data is a subset of the most important data in the Elysia decision tree. This includes the most pertinent information, such as the user prompt and the conversation history, as well as the _Atlas_, an alias to the style, agent description and end goal that the agent must adhere to, and the _environment_, a collection of all data retrieved or collected by Elysia during tool evaluations. A history of completed tasks and custom formatted text from the tools is also included. Any errors (that were manually caught and yielded) during tool evaluations are also included into the tree data, so that the decision agent or subsequent runs of the tools can be aware of previous failures. 32 | 33 | The tree data is included in the decision agent and can be also included in any LLM calls within tools. The tree data is essentially the way that tools 'interact' with Elysia, by including information of the state of the tree and updating this state information. 34 | 35 | To see a full breakdown of the tree data, [see the description for the `TreeData` class](../Reference/Objects.md#elysia.tree.objects.TreeData). 36 | -------------------------------------------------------------------------------- /docs/Examples/email.md: -------------------------------------------------------------------------------- 1 | # Sending an Automated Email 2 | 3 | This example will go through a -------------------------------------------------------------------------------- /docs/Examples/fantasy_adventure.md: -------------------------------------------------------------------------------- 1 | # Example: Creating a Fantasy Adventure Game 2 | 3 | Coming soon. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /docs/Examples/index.md: -------------------------------------------------------------------------------- 1 | 2 | 1. [Querying a Weaviate database](query_weaviate.md) - create an example collection in Weaviate and use Elysia with its core functionality to retrieve data. 3 | 2. [Basic Linear Regression](data_analysis.md) - creating a custom Elysia tool to perform a basic least squares regression on items retrieved from the `query` tool. 4 | 5 | More examples coming soon! -------------------------------------------------------------------------------- /docs/Examples/sentiment_analysis.md: -------------------------------------------------------------------------------- 1 | # Example: Sentiment Analysis on Retrieved Data 2 | 3 | Coming soon. 4 | 24 | 25 | -------------------------------------------------------------------------------- /docs/Reference/Client.md: -------------------------------------------------------------------------------- 1 | ::: elysia.util.client -------------------------------------------------------------------------------- /docs/Reference/Managers.md: -------------------------------------------------------------------------------- 1 | ::: elysia.api.services.user 2 | ::: elysia.api.services.tree -------------------------------------------------------------------------------- /docs/Reference/Objects.md: -------------------------------------------------------------------------------- 1 | ::: elysia.objects 2 | ::: elysia.tree.objects 3 | -------------------------------------------------------------------------------- /docs/Reference/PayloadTypes.md: -------------------------------------------------------------------------------- 1 | Below is the full contents of the `return_types.py` file, which specifies the different ways that Elysia sends results whenever `tree.async_run()` yields an object. This is primarily intended for interacting with the frontend - giving the frontend a specific 'payload type' and ensuring that all objects sent conform to the same 'mapping'. 2 | 3 | Each individual payload type has its own mapping. In this file, the keys of each dictionary are the allowed fields, and the values are descriptions for each field. The `specific_return_types` has broad descriptions of each class. 4 | 5 | ```python 6 | --8<-- "elysia/util/return_types.py" 7 | ``` -------------------------------------------------------------------------------- /docs/Reference/Preprocessor.md: -------------------------------------------------------------------------------- 1 | ::: elysia.preprocessing.collection -------------------------------------------------------------------------------- /docs/Reference/Settings.md: -------------------------------------------------------------------------------- 1 | ::: elysia.config -------------------------------------------------------------------------------- /docs/Reference/Tree.md: -------------------------------------------------------------------------------- 1 | ::: elysia.tree.tree -------------------------------------------------------------------------------- /docs/Reference/Util.md: -------------------------------------------------------------------------------- 1 | ::: elysia.util.elysia_chain_of_thought -------------------------------------------------------------------------------- /docs/basic.md: -------------------------------------------------------------------------------- 1 | # Basic Example 2 | 3 | Let's assume we have access to the following Weaviate collections: 4 | 5 | - `ecommerce`: a fashion dataset with fields such as `price`, `category`, `description`, etc. 6 | 7 | - `ml_wikipedia`: a collection of long Wikipedia articles related to machine learning, with fields such as `categories`, `content`, `title` etc. 8 | 9 | - `Tickets`: Github issues for a fictional company 10 | 11 | These Weaviate collections are those which we want to search over using Elysia. 12 | 13 | ## Setup 14 | 15 | You need to specify what models you want to use, as well as any API keys. To set up the models, you can use `configure`. For example, if you want to use the GPT-4o family of models: 16 | 17 | ```python 18 | from elysia import configure 19 | configure( 20 | base_model="gpt-4o-mini", 21 | base_provider="openai", 22 | complex_model="gpt-4o", 23 | complex_provider="openai", 24 | openai_api_key="sk-...", # replace with your API key 25 | wcd_url="...", # replace with your weaviate cloud url 26 | wcd_api_key="..." # replace with your weaviate cloud api key 27 | ) 28 | ``` 29 | 30 | You need to specify both a `base_model` and a `complex_model`, as well as their providers. This hooks into LiteLLM through DSPy, [so any LiteLLM supported models and providers will work](https://docs.litellm.ai/docs/providers). [See the setup page for more details](setting_up.md). 31 | 32 | Then for a collection to be accessible within Elysia, we need to preprocess it - so that the models are aware of the schemas and information about the collection. 33 | 34 | ```python 35 | from elysia import preprocess 36 | preprocess("Tickets") 37 | ``` 38 | 39 | ## Running Elysia 40 | 41 | To run the Elysia decision tree, using the default setup, just call the `Tree` object! 42 | 43 | ```python 44 | from elysia import Tree 45 | tree = Tree() 46 | response, objects = tree("what were the 10 most recent Github issues?") 47 | ``` 48 | Elysia will _dynamically_ run through the decision tree, choosing tools to use based on the decision agent LM based on the input. The decision tree returns the concatenation of all text responses from the models, as well as any retrieved objects (anything that was added to the environment during this call). 49 | 50 | ```python 51 | print(response) 52 | ``` 53 | ``` 54 | I will now query the Tickets collection to retrieve the 10 most recent issues for you. I applied a descending sort on the "issue_created_at" field to retrieve the 10 most recent issues. I will now summarize the 10 most recent Github issues for you. The latest tickets reflect ongoing discussions and developments within the Verba project. Notable entries include a closed issue regarding the use of specific model inputs, a report on a breaking change affecting the code chunker functionality, and requests for enhancements like custom JSON support and improved metadata handling during file uploads. The issues also highlight user concerns about application performance when processing large document uploads and the integration of external language models. Overall, these issues illustrate a dynamic environment with active contributions and feedback from the community. 55 | ``` 56 | ```python 57 | print(objects) 58 | ``` 59 | ``` 60 | [ 61 | [ 62 | { 63 | 'issue_id': 2843638219.0, 64 | 'issue_content': "If you set OLLAMA_MODEL and OLLAMA_EMBED_MODEL they will be the ones suggested instead of the first on Ollama's list.", 65 | 'issue_created_at': '2025-02-10T21:06:00Z', 66 | 'issue_labels': [], 67 | 'issue_url': 'https://github.com/weaviate/Verba/pull/372', 68 | 'issue_comments': 0.0, 69 | 'issue_title': 'use OLLAMA_MODEL OLLAMA_EMBED_MODEL as input suggestion when using Ollama', 70 | 'issue_author': 'dudanogueira', 71 | 'issue_updated_at': '2025-02-27T10:38:02Z', 72 | 'issue_state': 'closed', 73 | 'uuid': 'bc56b4b2fc6a541c94969721bd895a7c', 74 | 'ELYSIA_SUMMARY': '' 75 | }, 76 | ... 77 | { 78 | 'issue_id': 2845625123.0, 79 | 'issue_content': "Pull request for feature #2.", 80 | 'issue_created_at': '2025-01-4T22:16:05Z', 81 | 'issue_labels': [], 82 | 'issue_url': 'https://github.com/weaviate/Verba/pull/373', 83 | 'issue_comments': 2.0, 84 | 'issue_title': 'Feature PR #2', 85 | 'issue_author': 'thomashacker', 86 | 'issue_updated_at': '2025-01-15T11:06:08Z', 87 | 'issue_state': 'closed', 88 | 'uuid': '05dae4214e9050a59d4e9985892cdc10', 89 | 'ELYSIA_SUMMARY': '' 90 | } 91 | ] 92 | ] 93 | ``` -------------------------------------------------------------------------------- /docs/img/diabetes-regression-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/docs/img/diabetes-regression-example.png -------------------------------------------------------------------------------- /docs/img/regression-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/docs/img/regression-example.png -------------------------------------------------------------------------------- /docs/img/technical_overview_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/docs/img/technical_overview_1.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to Elysia 2 | 3 | Elysia is an agentic platform designed to use tools in a decision tree. A decision agent decides which tools to use dynamically based on its environment and context. You can use custom tools or use the pre-built tools designed to retrieve your data in a Weaviate cluster. 4 | 5 | See the [basic example to get started right away!](basic.md) Or if you want to make your own tools and customise Elysia, see how to [easily add your own tools](creating_tools.md). 6 | 7 | ## Get Started 8 | 9 | To use Elysia, you need to either set up your models and API keys in your `.env` file, or specify them in the config. [See the setup page to get started.](setting_up.md) 10 | 11 | Elysia can be used very simply: 12 | ```python 13 | from elysia import tool, Tree 14 | 15 | tree = Tree() 16 | 17 | @tool(tree=tree) 18 | async def add(x: int, y: int) -> int: 19 | return x + y 20 | 21 | tree("What is the sum of 9009 and 6006?") 22 | ``` 23 | 24 | Elysia is pre-configured to be capable of connecting to and interacting with your [Weaviate](https://weaviate.io/deployment/serverless) clusters! 25 | ```python 26 | from elysia import Tree 27 | tree = Tree() 28 | response, objects = tree( 29 | "What are the 10 most expensive items in the Ecommerce collection?", 30 | collection_names = ["Ecommerce"] 31 | ) 32 | ``` 33 | This will use the built-in open source _query_ tool or _aggregate_ tool to interact with your Weaviate collections. To get started connecting to Weaviate, [see the setting up page](setting_up.md#weaviate-integration). 34 | 35 | ## Installation 36 | 37 | ```bash 38 | pip install elysia-ai 39 | ``` 40 | 41 | ## Usage 42 | 43 | Elysia is free, open source, and available to anyone. 44 | 45 | Unlike other agent-based packages, Elysia is pre-configured to run a wide range of tools and has a lot of capabilities straight away. For example, you could just call Elysia on your Weaviate collections and it will immediately and dynamically search your data, using custom queries with filters or aggregations. 46 | 47 | Or you could customise Elysia to your liking, create your own custom tools and add them to the Elysia decision tree. 48 | 49 | To use Elysia to search your data, you need to either have [a Weaviate cloud cluster or a locally running Weaviate instance](setting_up.md#weaviate-integration) (or you can define your own custom tool to search another data source!). 50 | 51 | [Sign up to Weaviate Cloud! A 14 day sandbox cluster is free.](https://weaviate.io/deployment/serverless) 52 | 53 | For more information on signing up to Weaviate Cloud, [click here](https://weaviate.io/developers/wcs/platform/create-account). 54 | 55 | From your Weaviate cloud cluster, you can upload data via a CSV on the cloud console, or [you can upload via the Weaviate APIs](https://weaviate.io/developers/academy/py/zero_to_mvp/schema_and_imports/import). 56 | 57 | ## About 58 | 59 | Check out the Github Repositories for the backend and the frontend 60 | 61 | - [elysia](https://github.com/weaviate/elysia) (backend) 62 | 63 | - [elysia-frontend](https://github.com/weaviate/elysia-frontend) (frontend) 64 | 65 | Elysia was developed by Edward Schmuhl (frontend) and Danny Williams (backend). Check out our socials below: 66 | 67 | - [Edward's Linkedin](https://www.linkedin.com/in/edwardschmuhl/) 68 | 69 | - [Danny's Linkedin](https://www.linkedin.com/in/dannyjameswilliams/) 70 | 71 | Documentation built with [mkdocs](https://www.mkdocs.org). -------------------------------------------------------------------------------- /elysia/__init__.py: -------------------------------------------------------------------------------- 1 | from elysia.__metadata__ import ( 2 | __version__, 3 | __name__, 4 | __description__, 5 | __url__, 6 | __author__, 7 | __author_email__, 8 | ) 9 | 10 | from elysia.tree.tree import Tree 11 | from elysia.objects import ( 12 | Tool, 13 | Return, 14 | Text, 15 | Response, 16 | Update, 17 | Status, 18 | Warning, 19 | Error, 20 | Completed, 21 | Result, 22 | Retrieval, 23 | tool, 24 | ) 25 | from elysia.preprocessing.collection import ( 26 | preprocess, 27 | preprocessed_collection_exists, 28 | edit_preprocessed_collection, 29 | delete_preprocessed_collection, 30 | view_preprocessed_collection, 31 | ) 32 | from elysia.config import Settings, settings, configure, smart_setup, set_from_env 33 | -------------------------------------------------------------------------------- /elysia/__metadata__.py: -------------------------------------------------------------------------------- 1 | __name__ = "elysia" 2 | __version__ = "0.1.0.dev6" 3 | __description__ = "Elysia" 4 | __url__ = "https://github.com/weaviate/elysia" 5 | __author__ = "Danny Williams" 6 | __author_email__ = "danny@weaviate.io" 7 | -------------------------------------------------------------------------------- /elysia/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/__init__.py -------------------------------------------------------------------------------- /elysia/api/api_types.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List, Optional, Literal 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class QueryData(BaseModel): 7 | user_id: str 8 | conversation_id: str 9 | query_id: str 10 | query: str 11 | collection_names: list[str] 12 | route: Optional[str] = "" 13 | mimick: Optional[bool] = False 14 | 15 | 16 | class ViewPaginatedCollectionData(BaseModel): 17 | page_size: int 18 | page_number: int 19 | query: str = "" 20 | sort_on: Optional[str] = None 21 | ascending: bool = False 22 | filter_config: dict[str, Any] = {} 23 | 24 | 25 | class InitialiseTreeData(BaseModel): 26 | low_memory: bool = False 27 | 28 | 29 | class MetadataNamedVectorData(BaseModel): 30 | name: str 31 | enabled: Optional[bool] = None 32 | description: Optional[str] = None 33 | 34 | 35 | class MetadataFieldData(BaseModel): 36 | name: str 37 | description: str 38 | 39 | 40 | class UpdateCollectionMetadataData(BaseModel): 41 | named_vectors: Optional[List[MetadataNamedVectorData]] = None 42 | summary: Optional[str] = None 43 | mappings: Optional[dict[str, dict[str, str]]] = None 44 | fields: Optional[List[MetadataFieldData]] = None 45 | 46 | 47 | class NERData(BaseModel): 48 | text: str 49 | 50 | 51 | class TitleData(BaseModel): 52 | user_id: str 53 | conversation_id: str 54 | text: str 55 | 56 | 57 | class ObjectRelevanceData(BaseModel): 58 | user_id: str 59 | conversation_id: str 60 | query_id: str 61 | objects: list[dict] 62 | 63 | 64 | class ProcessCollectionData(BaseModel): 65 | user_id: str 66 | collection_name: str 67 | 68 | 69 | class DebugData(BaseModel): 70 | conversation_id: str 71 | user_id: str 72 | 73 | 74 | class AddFeedbackData(BaseModel): 75 | user_id: str 76 | conversation_id: str 77 | query_id: str 78 | feedback: int 79 | 80 | 81 | class RemoveFeedbackData(BaseModel): 82 | user_id: str 83 | conversation_id: str 84 | query_id: str 85 | 86 | 87 | class GetUserRequestsData(BaseModel): 88 | user_id: str 89 | 90 | 91 | class InstantReplyData(BaseModel): 92 | user_id: str 93 | user_prompt: str 94 | 95 | 96 | class FollowUpSuggestionsData(BaseModel): 97 | user_id: str 98 | conversation_id: str 99 | 100 | 101 | # class BackendConfig(BaseModel): 102 | # settings: dict[str, Any] 103 | # style: str 104 | # agent_description: str 105 | # end_goal: str 106 | # branch_initialisation: str 107 | 108 | 109 | class SaveConfigUserData(BaseModel): 110 | name: str 111 | config: dict[str, Any] 112 | frontend_config: dict[str, Any] 113 | default: bool 114 | 115 | 116 | class SaveConfigTreeData(BaseModel): 117 | settings: Optional[dict[str, Any]] = None 118 | style: Optional[str] = None 119 | agent_description: Optional[str] = None 120 | end_goal: Optional[str] = None 121 | branch_initialisation: Optional[str] = None 122 | 123 | 124 | class UpdateFrontendConfigData(BaseModel): 125 | config: dict[str, Any] 126 | 127 | 128 | class AddToolToTreeData(BaseModel): 129 | tool_name: str 130 | branch_id: str 131 | from_tool_ids: list[str] 132 | 133 | 134 | class RemoveToolFromTreeData(BaseModel): 135 | tool_name: str 136 | branch_id: str 137 | from_tool_ids: list[str] 138 | 139 | 140 | class AddBranchToTreeData(BaseModel): 141 | id: str 142 | description: str 143 | instruction: str 144 | from_branch_id: str 145 | from_tool_ids: list[str] 146 | status: str 147 | root: bool 148 | 149 | 150 | class RemoveBranchFromTreeData(BaseModel): 151 | id: str 152 | 153 | 154 | class AvailableModelsData(BaseModel): 155 | user_id: str 156 | -------------------------------------------------------------------------------- /elysia/api/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from contextlib import asynccontextmanager 3 | 4 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 5 | 6 | from fastapi import FastAPI 7 | from fastapi.middleware.cors import CORSMiddleware 8 | from fastapi.staticfiles import StaticFiles 9 | from fastapi.responses import FileResponse 10 | 11 | from elysia.api.core.log import logger, set_log_level 12 | from elysia.api.dependencies.common import get_user_manager 13 | from elysia.api.middleware.error_handlers import register_error_handlers 14 | from elysia.api.routes import ( 15 | collections, 16 | feedback, 17 | init, 18 | processor, 19 | query, 20 | user_config, 21 | tree_config, 22 | utils, 23 | tools, 24 | db, 25 | ) 26 | from elysia.api.services.user import UserManager 27 | from elysia.api.utils.resources import print_resources 28 | 29 | 30 | from pathlib import Path 31 | 32 | 33 | async def check_timeouts(): 34 | user_manager = get_user_manager() 35 | await user_manager.check_all_trees_timeout() 36 | 37 | 38 | async def output_resources(): 39 | user_manager = get_user_manager() 40 | await print_resources(user_manager, save_to_file=True) 41 | 42 | 43 | async def check_restart_clients(): 44 | user_manager = get_user_manager() 45 | await user_manager.check_restart_clients() 46 | 47 | 48 | @asynccontextmanager 49 | async def lifespan(app: FastAPI): 50 | user_manager = get_user_manager() 51 | 52 | scheduler = AsyncIOScheduler() 53 | set_log_level("INFO") 54 | 55 | # use prime numbers for intervals so they don't overlap 56 | scheduler.add_job(check_timeouts, "interval", seconds=29) 57 | scheduler.add_job(check_restart_clients, "interval", seconds=31) 58 | scheduler.add_job(output_resources, "interval", seconds=1103) 59 | 60 | scheduler.start() 61 | yield 62 | scheduler.shutdown() 63 | 64 | await user_manager.close_all_clients() 65 | 66 | 67 | # Create FastAPI app instance 68 | app = FastAPI(title="Elysia API", version="0.3.0", lifespan=lifespan) 69 | 70 | # Add CORS middleware 71 | app.add_middleware( 72 | CORSMiddleware, 73 | allow_origins=["*"], 74 | allow_credentials=True, 75 | allow_methods=["*"], 76 | allow_headers=["*"], 77 | ) 78 | 79 | # Register error handlers 80 | register_error_handlers(app) 81 | 82 | # Include routers 83 | app.include_router(init.router, prefix="/init", tags=["init"]) 84 | app.include_router(query.router, prefix="/ws", tags=["websockets"]) 85 | app.include_router(processor.router, prefix="/ws", tags=["websockets"]) 86 | app.include_router(collections.router, prefix="/collections", tags=["collections"]) 87 | app.include_router(user_config.router, prefix="/user/config", tags=["user config"]) 88 | app.include_router(tree_config.router, prefix="/tree/config", tags=["tree config"]) 89 | app.include_router(feedback.router, prefix="/feedback", tags=["feedback"]) 90 | app.include_router(utils.router, prefix="/util", tags=["utilities"]) 91 | app.include_router(tools.router, prefix="/tools", tags=["tools"]) 92 | app.include_router(db.router, prefix="/db", tags=["db"]) 93 | 94 | 95 | # Health check endpoint (kept in main app.py due to its simplicity) 96 | @app.get("/api/health", tags=["health"]) 97 | async def health_check(): 98 | """Health check endpoint.""" 99 | logger.info("Health check requested") 100 | return {"status": "healthy"} 101 | 102 | 103 | # Mount the app from static files 104 | BASE_DIR = Path(__file__).resolve().parent 105 | 106 | # Serve NextJS _next assets at root level (this is crucial!) 107 | app.mount( 108 | "/_next", 109 | StaticFiles(directory=BASE_DIR / "static/_next"), 110 | name="next-assets", 111 | ) 112 | 113 | # Serve other static files 114 | app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="app") 115 | 116 | 117 | @app.get("/") 118 | @app.head("/") 119 | async def serve_frontend(): 120 | if os.path.exists(os.path.join(BASE_DIR, "static/index.html")): 121 | return FileResponse(os.path.join(BASE_DIR, "static/index.html")) 122 | else: 123 | return None 124 | -------------------------------------------------------------------------------- /elysia/api/cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import set_key, load_dotenv 3 | from rich import print 4 | 5 | load_dotenv() 6 | 7 | if "FIRST_START_ELYSIA" not in os.environ: 8 | print( 9 | "\n\n[bold green]Starting Elysia for the first time. This may take a minute to complete...[/bold green]\n\n" 10 | ) 11 | set_key(".env", "FIRST_START_ELYSIA", "1") 12 | 13 | import click 14 | import uvicorn 15 | 16 | 17 | @click.group() 18 | def cli(): 19 | """Main command group for Elysia.""" 20 | pass 21 | 22 | 23 | @cli.command() 24 | @click.option( 25 | "--port", 26 | default=8000, 27 | help="FastAPI Port", 28 | ) 29 | @click.option( 30 | "--host", 31 | default="localhost", 32 | help="FastAPI Host", 33 | ) 34 | @click.option( 35 | "--reload", 36 | default=True, 37 | help="FastAPI Reload", 38 | ) 39 | def start(port, host, reload): 40 | """ 41 | Run the FastAPI application. 42 | """ 43 | 44 | uvicorn.run( 45 | "elysia.api.app:app", 46 | host=host, 47 | port=port, 48 | reload=reload, 49 | ) 50 | 51 | 52 | if __name__ == "__main__": 53 | cli() 54 | -------------------------------------------------------------------------------- /elysia/api/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/core/__init__.py -------------------------------------------------------------------------------- /elysia/api/core/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from rich.logging import RichHandler 3 | import litellm 4 | import dspy 5 | 6 | # Initialize the logger as a module-level variable 7 | _logger = None 8 | 9 | 10 | def get_logger(): 11 | """Get the singleton logger instance.""" 12 | global _logger 13 | if _logger is None: 14 | # Configure basic logging if not already configured 15 | if not logging.root.handlers: 16 | FORMAT = "%(message)s" 17 | logging.basicConfig( 18 | level="DEBUG", 19 | format=FORMAT, 20 | datefmt="[%X]", 21 | handlers=[RichHandler(rich_tracebacks=True, markup=True)], 22 | ) 23 | 24 | # Set log levels for specific loggers 25 | logging.getLogger("httpx").setLevel(logging.WARNING) 26 | logging.getLogger("httpcore").setLevel(logging.WARNING) 27 | logging.getLogger("urllib3").setLevel(logging.WARNING) 28 | logging.getLogger("asyncio").setLevel(logging.INFO) 29 | logging.getLogger("fastapi").setLevel(logging.INFO) 30 | logging.getLogger("uvicorn").setLevel(logging.INFO) 31 | logging.getLogger("starlette").setLevel(logging.INFO) 32 | logging.getLogger("grpc").setLevel(logging.INFO) 33 | logging.getLogger("grpc._channel").setLevel(logging.WARNING) 34 | logging.getLogger("dspy").setLevel(logging.WARNING) 35 | logging.getLogger("dspy").propagate = False 36 | logging.getLogger("LiteLLM").setLevel(logging.WARNING) 37 | logging.getLogger("LiteLLM").propagate = False 38 | logging.getLogger("LiteLLM Router").setLevel(logging.WARNING) 39 | logging.getLogger("LiteLLM Router").propagate = False 40 | logging.getLogger("LiteLLM Proxy").setLevel(logging.WARNING) 41 | logging.getLogger("LiteLLM Proxy").propagate = False 42 | logging.getLogger("matplotlib").setLevel(logging.WARNING) 43 | logging.getLogger("matplotlib").propagate = False 44 | 45 | _logger = logging.getLogger("rich") 46 | _logger.setLevel(logging.DEBUG) 47 | 48 | return _logger 49 | 50 | 51 | def set_log_level(level: str): 52 | """Set the level for the main application logger.""" 53 | logger = get_logger() 54 | logger.setLevel(level) 55 | 56 | 57 | # Initialize the logger 58 | logger = get_logger() 59 | 60 | dspy.disable_litellm_logging() 61 | dspy.disable_logging() 62 | litellm.suppress_debug_info = True 63 | -------------------------------------------------------------------------------- /elysia/api/custom_tools.py: -------------------------------------------------------------------------------- 1 | from elysia import Tool 2 | from elysia.objects import Response 3 | 4 | # Import a custom tool from a separate file 5 | from elysia.tools.visualisation.linear_regression import BasicLinearRegression 6 | 7 | # Import existing tools 8 | from elysia.tools.retrieval.query import Query 9 | from elysia.tools.retrieval.aggregate import Aggregate 10 | from elysia.tools.text.text import CitedSummarizer, FakeTextResponse 11 | 12 | 13 | # Or you can define the tool inline here 14 | class TellAJoke(Tool): 15 | """ 16 | Example tool for testing/demonstration purposes. 17 | Simply returns a joke as a text response that was an input to the tool. 18 | """ 19 | 20 | def __init__(self, **kwargs): 21 | 22 | # Init requires initialisation of the super class (Tool) 23 | super().__init__( 24 | name="tell_a_joke", 25 | description="Displays a joke to the user.", 26 | inputs={ 27 | "joke": { 28 | "type": str, 29 | "description": "A joke to tell.", 30 | "required": True, 31 | } 32 | }, 33 | end=True, 34 | ) 35 | 36 | # Call must be a async generator function that yields objects to the decision tree 37 | async def __call__( 38 | self, tree_data, inputs, base_lm, complex_lm, client_manager, **kwargs 39 | ): 40 | 41 | # This example tool only returns the input to the tool, so is not very useful 42 | yield Response(inputs["joke"]) 43 | 44 | # You can include more complex logic here via a custom function 45 | -------------------------------------------------------------------------------- /elysia/api/dependencies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/dependencies/__init__.py -------------------------------------------------------------------------------- /elysia/api/dependencies/common.py: -------------------------------------------------------------------------------- 1 | from elysia.api.services.user import UserManager 2 | 3 | # Create the singleton instance at module level 4 | user_manager = UserManager() 5 | 6 | 7 | def get_user_manager() -> UserManager: 8 | """Get the singleton UserManager instance.""" 9 | return user_manager 10 | -------------------------------------------------------------------------------- /elysia/api/middleware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/middleware/__init__.py -------------------------------------------------------------------------------- /elysia/api/middleware/error_handlers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from fastapi import FastAPI, Request 4 | from fastapi.exceptions import RequestValidationError 5 | from fastapi.responses import JSONResponse 6 | from starlette.exceptions import HTTPException as StarletteHTTPException 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def register_error_handlers(app: FastAPI): 12 | @app.exception_handler(StarletteHTTPException) 13 | async def http_exception_handler(request: Request, exc: StarletteHTTPException): 14 | logger.error(f"HTTP error occurred: {exc.detail}") 15 | return JSONResponse( 16 | status_code=exc.status_code, 17 | content={"detail": exc.detail}, 18 | ) 19 | 20 | @app.exception_handler(RequestValidationError) 21 | async def validation_exception_handler( 22 | request: Request, exc: RequestValidationError 23 | ): 24 | logger.error(f"Validation error: {exc.errors()}") 25 | return JSONResponse( 26 | status_code=422, 27 | content={"detail": exc.errors()}, 28 | ) 29 | 30 | @app.exception_handler(Exception) 31 | async def general_exception_handler(request: Request, exc: Exception): 32 | logger.error(f"Unexpected error: {str(exc)}") 33 | return JSONResponse( 34 | status_code=500, 35 | content={"detail": "Internal Server Error"}, 36 | ) 37 | -------------------------------------------------------------------------------- /elysia/api/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/routes/__init__.py -------------------------------------------------------------------------------- /elysia/api/routes/db.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | from fastapi.responses import JSONResponse 3 | 4 | from elysia.api.dependencies.common import get_user_manager 5 | from elysia.api.services.user import UserManager 6 | 7 | # Logging 8 | from elysia.api.core.log import logger 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.get("/{user_id}/saved_trees") 14 | async def get_saved_trees( 15 | user_id: str, 16 | user_manager: UserManager = Depends(get_user_manager), 17 | ): 18 | 19 | headers = {"Cache-Control": "no-cache"} 20 | 21 | user = await user_manager.get_user_local(user_id) 22 | save_location_client_manager = user["frontend_config"].save_location_client_manager 23 | if not save_location_client_manager.is_client: 24 | logger.warning( 25 | "In /get_saved_trees API, " 26 | "no valid destination for trees location found. " 27 | "Returning no error but an empty list of trees." 28 | ) 29 | return JSONResponse( 30 | content={"trees": {}, "error": ""}, 31 | status_code=200, 32 | headers=headers, 33 | ) 34 | 35 | try: 36 | trees = await user_manager.get_saved_trees( 37 | user_id, save_location_client_manager 38 | ) 39 | return JSONResponse( 40 | content={"trees": trees, "error": ""}, status_code=200, headers=headers 41 | ) 42 | 43 | except Exception as e: 44 | logger.error(f"Error getting saved trees: {str(e)}") 45 | return JSONResponse( 46 | content={"trees": {}, "error": str(e)}, status_code=500, headers=headers 47 | ) 48 | 49 | 50 | @router.get("/{user_id}/load_tree/{conversation_id}") 51 | async def load_tree( 52 | user_id: str, 53 | conversation_id: str, 54 | user_manager: UserManager = Depends(get_user_manager), 55 | ): 56 | 57 | headers = {"Cache-Control": "no-cache"} 58 | 59 | try: 60 | frontend_rebuild = await user_manager.load_tree(user_id, conversation_id) 61 | return JSONResponse( 62 | content={"rebuild": frontend_rebuild, "error": ""}, 63 | status_code=200, 64 | headers=headers, 65 | ) 66 | except Exception as e: 67 | logger.error(f"Error loading tree: {str(e)}") 68 | return JSONResponse( 69 | content={"rebuild": [], "error": str(e)}, 70 | status_code=500, 71 | headers=headers, 72 | ) 73 | 74 | 75 | @router.post("/{user_id}/save_tree/{conversation_id}") 76 | async def save_tree( 77 | user_id: str, 78 | conversation_id: str, 79 | user_manager: UserManager = Depends(get_user_manager), 80 | ): 81 | try: 82 | await user_manager.save_tree(user_id, conversation_id) 83 | return JSONResponse(content={"error": ""}, status_code=200) 84 | except Exception as e: 85 | logger.error(f"Error saving tree: {str(e)}") 86 | return JSONResponse(content={"error": str(e)}, status_code=500) 87 | 88 | 89 | @router.delete("/{user_id}/delete_tree/{conversation_id}") 90 | async def delete_tree( 91 | user_id: str, 92 | conversation_id: str, 93 | user_manager: UserManager = Depends(get_user_manager), 94 | ): 95 | try: 96 | await user_manager.delete_tree(user_id, conversation_id) 97 | return JSONResponse(content={"error": ""}, status_code=200) 98 | except Exception as e: 99 | logger.error(f"Error deleting tree: {str(e)}") 100 | return JSONResponse(content={"error": str(e)}, status_code=500) 101 | -------------------------------------------------------------------------------- /elysia/api/routes/feedback.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | from fastapi.responses import JSONResponse 3 | 4 | from elysia.api.api_types import ( 5 | AddFeedbackData, 6 | RemoveFeedbackData, 7 | ) 8 | from elysia.api.dependencies.common import get_user_manager 9 | from elysia.api.services.user import UserManager 10 | from elysia.api.utils.feedback import ( 11 | create_feedback, 12 | feedback_metadata, 13 | remove_feedback, 14 | ) 15 | from elysia.api.core.log import logger 16 | from elysia.util.client import ClientManager 17 | 18 | router = APIRouter() 19 | 20 | 21 | @router.post("/add") 22 | async def run_add_feedback( 23 | data: AddFeedbackData, user_manager: UserManager = Depends(get_user_manager) 24 | ): 25 | logger.debug(f"/add_feedback API request received") 26 | logger.debug(f"User ID: {data.user_id}") 27 | logger.debug(f"Conversation ID: {data.conversation_id}") 28 | logger.debug(f"Query ID: {data.query_id}") 29 | logger.debug(f"Feedback: {data.feedback}") 30 | 31 | tree = await user_manager.get_tree(data.user_id, data.conversation_id) 32 | user = user_manager.users[data.user_id] 33 | client_manager: ClientManager = user["client_manager"] 34 | 35 | async with client_manager.connect_to_async_client() as client: 36 | try: 37 | await create_feedback( 38 | data.user_id, 39 | data.conversation_id, 40 | data.query_id, 41 | data.feedback, 42 | tree, 43 | client, 44 | ) 45 | except Exception as e: 46 | return JSONResponse(content={"error": str(e)}, status_code=500) 47 | 48 | return JSONResponse(content={"error": ""}, status_code=200) 49 | 50 | 51 | @router.post("/remove") 52 | async def run_remove_feedback( 53 | data: RemoveFeedbackData, user_manager: UserManager = Depends(get_user_manager) 54 | ): 55 | logger.debug(f"/remove_feedback API request received") 56 | logger.debug(f"User ID: {data.user_id}") 57 | logger.debug(f"Conversation ID: {data.conversation_id}") 58 | logger.debug(f"Query ID: {data.query_id}") 59 | 60 | user = user_manager.users[data.user_id] 61 | client_manager: ClientManager = user["client_manager"] 62 | 63 | async with client_manager.connect_to_async_client() as client: 64 | try: 65 | await remove_feedback( 66 | data.user_id, data.conversation_id, data.query_id, client 67 | ) 68 | return JSONResponse(content={"error": ""}, status_code=200) 69 | except Exception as e: 70 | return JSONResponse(content={"error": str(e)}, status_code=500) 71 | 72 | 73 | @router.get("/metadata/{user_id}") 74 | async def run_feedback_metadata( 75 | user_id: str, user_manager: UserManager = Depends(get_user_manager) 76 | ): 77 | logger.debug(f"/feedback_metadata API request received") 78 | logger.debug(f"User ID: {user_id}") 79 | 80 | user = user_manager.users[user_id] 81 | client_manager: ClientManager = user["client_manager"] 82 | 83 | async with client_manager.connect_to_async_client() as client: 84 | metadata = await feedback_metadata(client, user_id) 85 | 86 | return JSONResponse( 87 | content={ 88 | "error": "", 89 | **metadata, 90 | }, 91 | status_code=200, 92 | ) 93 | -------------------------------------------------------------------------------- /elysia/api/routes/processor.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from fastapi import APIRouter, Depends 4 | from fastapi.websockets import WebSocket, WebSocketDisconnect 5 | from typing import Callable 6 | import psutil 7 | import time 8 | 9 | # Logging 10 | from elysia.api.core.log import logger 11 | 12 | # User manager 13 | from elysia.api.dependencies.common import get_user_manager 14 | from elysia.api.services.user import UserManager 15 | 16 | # Websocket 17 | from elysia.api.utils.websocket import help_websocket 18 | 19 | # Preprocessing 20 | from elysia.preprocessing.collection import preprocess_async 21 | 22 | router = APIRouter() 23 | 24 | 25 | async def process_collection( 26 | data: dict, websocket: WebSocket, user_manager: UserManager 27 | ): 28 | logger.debug(f"/process_collection API request received") 29 | logger.debug(f"User ID: {data['user_id']}") 30 | logger.debug(f"Collection name: {data['collection_name']}") 31 | 32 | user = await user_manager.get_user_local(data["user_id"]) 33 | settings = user["tree_manager"].settings 34 | 35 | async for result in preprocess_async( 36 | collection_name=data["collection_name"], 37 | client_manager=user["client_manager"], 38 | force=True, 39 | settings=settings, 40 | ): 41 | try: 42 | logger.debug( 43 | f"(process_collection) sending result with progress: {result['progress']*100}%" 44 | ) 45 | await websocket.send_json(result) 46 | except WebSocketDisconnect: 47 | logger.info("Client disconnected during process_collection") 48 | break 49 | await asyncio.sleep(0.001) 50 | logger.debug(f"(process_collection) FINISHED!") 51 | 52 | 53 | @router.websocket("/process_collection") 54 | async def process_collection_websocket( 55 | websocket: WebSocket, user_manager: UserManager = Depends(get_user_manager) 56 | ): 57 | await help_websocket( 58 | websocket, lambda data, ws: process_collection(data, ws, user_manager) 59 | ) 60 | -------------------------------------------------------------------------------- /elysia/api/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/services/__init__.py -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/0e762574-c759a1561c192a17.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[779],{11140:function(c,t,a){a.d(t,{DaX:function(){return C},X8P:function(){return s},mx6:function(){return n}});var r=a(46231);function C(c){return(0,r.w_)({tag:"svg",attr:{fill:"currentColor",viewBox:"0 0 16 16"},child:[{tag:"path",attr:{d:"M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9 9 0 0 0 8 15"},child:[]}]})(c)}function n(c){return(0,r.w_)({tag:"svg",attr:{fill:"currentColor",viewBox:"0 0 16 16"},child:[{tag:"path",attr:{d:"M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m.5-5v1h1a.5.5 0 0 1 0 1h-1v1a.5.5 0 0 1-1 0v-1h-1a.5.5 0 0 1 0-1h1v-1a.5.5 0 0 1 1 0M8 1c-1.573 0-3.022.289-4.096.777C2.875 2.245 2 2.993 2 4s.875 1.755 1.904 2.223C4.978 6.711 6.427 7 8 7s3.022-.289 4.096-.777C13.125 5.755 14 5.007 14 4s-.875-1.755-1.904-2.223C11.022 1.289 9.573 1 8 1"},child:[]},{tag:"path",attr:{d:"M2 7v-.839c.457.432 1.004.751 1.49.972C4.722 7.693 6.318 8 8 8s3.278-.307 4.51-.867c.486-.22 1.033-.54 1.49-.972V7c0 .424-.155.802-.411 1.133a4.51 4.51 0 0 0-4.815 1.843A12 12 0 0 1 8 10c-1.573 0-3.022-.289-4.096-.777C2.875 8.755 2 8.007 2 7m6.257 3.998L8 11c-1.682 0-3.278-.307-4.51-.867-.486-.22-1.033-.54-1.49-.972V10c0 1.007.875 1.755 1.904 2.223C4.978 12.711 6.427 13 8 13h.027a4.55 4.55 0 0 1 .23-2.002m-.002 3L8 14c-1.682 0-3.278-.307-4.51-.867-.486-.22-1.033-.54-1.49-.972V13c0 1.007.875 1.755 1.904 2.223C4.978 15.711 6.427 16 8 16c.536 0 1.058-.034 1.555-.097a4.5 4.5 0 0 1-1.3-1.905"},child:[]}]})(c)}function s(c){return(0,r.w_)({tag:"svg",attr:{fill:"currentColor",viewBox:"0 0 16 16"},child:[{tag:"path",attr:{d:"M4.318 2.687C5.234 2.271 6.536 2 8 2s2.766.27 3.682.687C12.644 3.125 13 3.627 13 4c0 .374-.356.875-1.318 1.313C10.766 5.729 9.464 6 8 6s-2.766-.27-3.682-.687C3.356 4.875 3 4.373 3 4c0-.374.356-.875 1.318-1.313M13 5.698V7c0 .374-.356.875-1.318 1.313C10.766 8.729 9.464 9 8 9s-2.766-.27-3.682-.687C3.356 7.875 3 7.373 3 7V5.698c.271.202.58.378.904.525C4.978 6.711 6.427 7 8 7s3.022-.289 4.096-.777A5 5 0 0 0 13 5.698M14 4c0-1.007-.875-1.755-1.904-2.223C11.022 1.289 9.573 1 8 1s-3.022.289-4.096.777C2.875 2.245 2 2.993 2 4v9c0 1.007.875 1.755 1.904 2.223C4.978 15.71 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13zm-1 4.698V10c0 .374-.356.875-1.318 1.313C10.766 11.729 9.464 12 8 12s-2.766-.27-3.682-.687C3.356 10.875 3 10.373 3 10V8.698c.271.202.58.378.904.525C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777A5 5 0 0 0 13 8.698m0 3V13c0 .374-.356.875-1.318 1.313C10.766 14.729 9.464 15 8 15s-2.766-.27-3.682-.687C3.356 13.875 3 13.373 3 13v-1.302c.271.202.58.378.904.525C4.978 12.71 6.427 13 8 13s3.022-.289 4.096-.777c.324-.147.633-.323.904-.525"},child:[]}]})(c)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/30a37ab2-322b0b940673459b.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[957],{32909:function(c,a,t){t.d(a,{req:function(){return n}});var e=t(46231);function n(c){return(0,e.w_)({tag:"svg",attr:{role:"img",viewBox:"0 0 24 24"},child:[{tag:"path",attr:{d:"M12 2.862c-6.617 0-12 5.383-12 12 0 1.964.49 3.406 1.5 4.408 1.706 1.696 4.619 1.868 8.05 1.868.43 0 .87-.002 1.315-.005a217.6 217.6 0 0 1 2.765 0c3.792.024 7.066.044 8.88-1.758C23.511 18.378 24 16.9 24 14.862c0-6.617-5.383-12-12-12zm-8.852 8.154a.393.393 0 1 1 0-.787.393.393 0 0 1 0 .787zM5.113 8.48c-.55.637-1.01 1.361-1.01 1.361-.06.092-.167.099-.24.017l-.26-.29a.251.251 0 0 1-.02-.303s1.11-1.559 1.806-2.186c.25-.225.248-.239.891-.692.643-.453 1.4-.826 1.4-.826a.272.272 0 0 1 .308.059l.26.29c.075.082.056.186-.04.235 0 0-1.772.887-2.353 1.509-.394.422-.192.19-.742.826zm1.576 2.143a1.377 1.377 0 1 1 2.754 0 1.377 1.377 0 0 1-2.754 0zm5.41 7.929c-1.902 0-3.443-1.542-3.443-3.443s1.644-.854 3.545-.854 3.34-1.047 3.34.854-1.541 3.443-3.443 3.443zM16.72 12a1.377 1.377 0 1 1 0-2.754 1.377 1.377 0 0 1 0 2.754z"},child:[]}]})(c)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/479ba886-f85ef559edbf188b.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[259],{63400:function(c,t,a){a.d(t,{vc4:function(){return n}});var h=a(46231);function n(c){return(0,h.w_)({tag:"svg",attr:{viewBox:"0 0 1024 1024"},child:[{tag:"path",attr:{d:"M512 472a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm367 352.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 0 1-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.7-107.8c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1L813.5 844h-603z"},child:[]}]})(c)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/52ab8b6c-668757d47a6e6fc6.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[821],{92787:function(c,t,h){h.d(t,{b10:function(){return i},bqj:function(){return r}});var l=h(46231);function r(c){return(0,l.w_)({tag:"svg",attr:{version:"1.2",baseProfile:"tiny",viewBox:"0 0 24 24"},child:[{tag:"path",attr:{d:"M12 4c-4.419 0-8 3.582-8 8s3.581 8 8 8 8-3.582 8-8-3.581-8-8-8zm3.707 10.293c.391.391.391 1.023 0 1.414-.195.195-.451.293-.707.293s-.512-.098-.707-.293l-2.293-2.293-2.293 2.293c-.195.195-.451.293-.707.293s-.512-.098-.707-.293c-.391-.391-.391-1.023 0-1.414l2.293-2.293-2.293-2.293c-.391-.391-.391-1.023 0-1.414s1.023-.391 1.414 0l2.293 2.293 2.293-2.293c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-2.293 2.293 2.293 2.293z"},child:[]}]})(c)}function i(c){return(0,l.w_)({tag:"svg",attr:{version:"1.2",baseProfile:"tiny",viewBox:"0 0 24 24"},child:[{tag:"g",attr:{},child:[{tag:"path",attr:{d:"M20.756 5.345c-.191-.219-.466-.345-.756-.345h-13.819l-.195-1.164c-.08-.482-.497-.836-.986-.836h-2.25c-.553 0-1 .447-1 1s.447 1 1 1h1.403l1.86 11.164.045.124.054.151.12.179.095.112.193.13.112.065c.116.047.238.075.367.075h11.001c.553 0 1-.447 1-1s-.447-1-1-1h-10.153l-.166-1h11.319c.498 0 .92-.366.99-.858l1-7c.041-.288-.045-.579-.234-.797zm-1.909 1.655l-.285 2h-3.562v-2h3.847zm-4.847 0v2h-3v-2h3zm0 3v2h-3v-2h3zm-4-3v2h-3l-.148.03-.338-2.03h3.486zm-2.986 3h2.986v2h-2.653l-.333-2zm7.986 2v-2h3.418l-.285 2h-3.133z"},child:[]},{tag:"circle",attr:{cx:"8.5",cy:"19.5",r:"1.5"},child:[]},{tag:"circle",attr:{cx:"17.5",cy:"19.5",r:"1.5"},child:[]}]}]})(c)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/53c13509-fcf4cae42fb19e2e.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[240],{61273:function(t,c,l){l.d(c,{EhG:function(){return o},VF9:function(){return i},WHV:function(){return w},ZTc:function(){return a},dXY:function(){return f},gbA:function(){return s},kpq:function(){return d},l_A:function(){return h},lcJ:function(){return r},mwJ:function(){return u},tvD:function(){return e},wEH:function(){return g}});var n=l(46231);function r(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm297.1 84L257.3 234.6 379.4 396H283.8L209 298.1 123.3 396H75.8l111-126.9L69.7 116h98l67.7 89.5L313.6 116h47.5zM323.3 367.6L153.4 142.9H125.1L296.9 367.6h26.3z"},child:[]}]})(t)}function a(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 384 512"},child:[{tag:"path",attr:{d:"M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2 160 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-306.7L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"},child:[]}]})(t)}function i(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 384 512"},child:[{tag:"path",attr:{d:"M0 48V487.7C0 501.1 10.9 512 24.3 512c5 0 9.9-1.5 14-4.4L192 400 345.7 507.6c4.1 2.9 9 4.4 14 4.4c13.4 0 24.3-10.9 24.3-24.3V48c0-26.5-21.5-48-48-48H48C21.5 0 0 21.5 0 48z"},child:[]}]})(t)}function u(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64C0 46.3 14.3 32 32 32zm0 128l448 0 0 256c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-256zm128 80c0 8.8 7.2 16 16 16l160 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-160 0c-8.8 0-16 7.2-16 16z"},child:[]}]})(t)}function h(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"},child:[]}]})(t)}function s(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z"},child:[]}]})(t)}function e(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 640 512"},child:[{tag:"path",attr:{d:"M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"},child:[]}]})(t)}function d(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M96 96c0-35.3 28.7-64 64-64l288 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L80 480c-44.2 0-80-35.8-80-80L0 128c0-17.7 14.3-32 32-32s32 14.3 32 32l0 272c0 8.8 7.2 16 16 16s16-7.2 16-16L96 96zm64 24l0 80c0 13.3 10.7 24 24 24l112 0c13.3 0 24-10.7 24-24l0-80c0-13.3-10.7-24-24-24L184 96c-13.3 0-24 10.7-24 24zm208-8c0 8.8 7.2 16 16 16l48 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-48 0c-8.8 0-16 7.2-16 16zm0 96c0 8.8 7.2 16 16 16l48 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-48 0c-8.8 0-16 7.2-16 16zM160 304c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-256 0c-8.8 0-16 7.2-16 16zm0 96c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-256 0c-8.8 0-16 7.2-16 16z"},child:[]}]})(t)}function g(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z"},child:[]}]})(t)}function o(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M352 224c53 0 96-43 96-96s-43-96-96-96s-96 43-96 96c0 4 .2 8 .7 11.9l-94.1 47C145.4 170.2 121.9 160 96 160c-53 0-96 43-96 96s43 96 96 96c25.9 0 49.4-10.2 66.6-26.9l94.1 47c-.5 3.9-.7 7.8-.7 11.9c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-25.9 0-49.4 10.2-66.6 26.9l-94.1-47c.5-3.9 .7-7.8 .7-11.9s-.2-8-.7-11.9l94.1-47C302.6 213.8 326.1 224 352 224z"},child:[]}]})(t)}function w(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M64 256l0-96 160 0 0 96L64 256zm0 64l160 0 0 96L64 416l0-96zm224 96l0-96 160 0 0 96-160 0zM448 256l-160 0 0-96 160 0 0 96zM64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32z"},child:[]}]})(t)}function f(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 640 512"},child:[{tag:"path",attr:{d:"M128 64c0-17.7 14.3-32 32-32l160 0c17.7 0 32 14.3 32 32l0 352 96 0 0-160c0-17.7 14.3-32 32-32l128 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0 0 160c0 17.7-14.3 32-32 32l-160 0c-17.7 0-32-14.3-32-32l0-352-96 0 0 160c0 17.7-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0 0-160z"},child:[]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/59650de3-4892ddf82b39e2a7.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[212],{48637:function(t,r,n){n.d(r,{AgU:function(){return d},J0f:function(){return e},WrU:function(){return h},dxO:function(){return k},gPZ:function(){return o},nbc:function(){return u},pdZ:function(){return i},rv8:function(){return g},u3W:function(){return l},wNS:function(){return s},yY4:function(){return c}});var a=n(46231);function o(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"m6 9 6 6 6-6"},child:[]}]})(t)}function i(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"ellipse",attr:{cx:"12",cy:"5",rx:"9",ry:"3"},child:[]},{tag:"path",attr:{d:"M3 5V19A9 3 0 0 0 21 19V5"},child:[]},{tag:"path",attr:{d:"M3 12A9 3 0 0 0 21 12"},child:[]}]})(t)}function e(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M12 17h.01"},child:[]},{tag:"path",attr:{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"},child:[]},{tag:"path",attr:{d:"M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3"},child:[]}]})(t)}function d(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"},child:[]},{tag:"path",attr:{d:"M14 2v4a2 2 0 0 0 2 2h4"},child:[]},{tag:"path",attr:{d:"M10 9H8"},child:[]},{tag:"path",attr:{d:"M16 13H8"},child:[]},{tag:"path",attr:{d:"M16 17H8"},child:[]}]})(t)}function c(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"},child:[]},{tag:"path",attr:{d:"M13 8H7"},child:[]},{tag:"path",attr:{d:"M17 12H7"},child:[]}]})(t)}function h(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"},child:[]}]})(t)}function l(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z"},child:[]},{tag:"path",attr:{d:"M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"},child:[]}]})(t)}function u(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"rect",attr:{width:"18",height:"18",x:"3",y:"3",rx:"2"},child:[]},{tag:"path",attr:{d:"M9 3v18"},child:[]}]})(t)}function s(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"},child:[]},{tag:"path",attr:{d:"M21 3v5h-5"},child:[]},{tag:"path",attr:{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"},child:[]},{tag:"path",attr:{d:"M8 16H3v5"},child:[]}]})(t)}function g(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M20 7h-9"},child:[]},{tag:"path",attr:{d:"M14 17H5"},child:[]},{tag:"circle",attr:{cx:"17",cy:"17",r:"3"},child:[]},{tag:"circle",attr:{cx:"7",cy:"7",r:"3"},child:[]}]})(t)}function k(t){return(0,a.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"m4.5 8 10.58-5.06a1 1 0 0 1 1.342.488L18.5 8"},child:[]},{tag:"path",attr:{d:"M6 10V8"},child:[]},{tag:"path",attr:{d:"M6 14v1"},child:[]},{tag:"path",attr:{d:"M6 19v2"},child:[]},{tag:"rect",attr:{x:"2",y:"8",width:"20",height:"13",rx:"2"},child:[]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/5e22fd23-dcf8510321a066a0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[452],{94295:function(t,c,n){n.d(c,{CcW:function(){return o},D0w:function(){return e},Lgw:function(){return u},QAE:function(){return g},SVk:function(){return l},be3:function(){return i},fWh:function(){return a},mpJ:function(){return d},oRX:function(){return h},qVU:function(){return v}});var r=n(46231);function a(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M362.6 192.9L345 174.8c-.7-.8-1.8-1.2-2.8-1.2-1.1 0-2.1.4-2.8 1.2l-122 122.9-44.4-44.4c-.8-.8-1.8-1.2-2.8-1.2-1 0-2 .4-2.8 1.2l-17.8 17.8c-1.6 1.6-1.6 4.1 0 5.7l56 56c3.6 3.6 8 5.7 11.7 5.7 5.3 0 9.9-3.9 11.6-5.5h.1l133.7-134.4c1.4-1.7 1.4-4.2-.1-5.7z"},child:[]},{tag:"path",attr:{d:"M256 76c48.1 0 93.3 18.7 127.3 52.7S436 207.9 436 256s-18.7 93.3-52.7 127.3S304.1 436 256 436c-48.1 0-93.3-18.7-127.3-52.7S76 304.1 76 256s18.7-93.3 52.7-127.3S207.9 76 256 76m0-28C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48z"},child:[]}]})(t)}function i(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M256 48C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48zm106.5 150.5L228.8 332.8h-.1c-1.7 1.7-6.3 5.5-11.6 5.5-3.8 0-8.1-2.1-11.7-5.7l-56-56c-1.6-1.6-1.6-4.1 0-5.7l17.8-17.8c.8-.8 1.8-1.2 2.8-1.2 1 0 2 .4 2.8 1.2l44.4 44.4 122-122.9c.8-.8 1.8-1.2 2.8-1.2 1.1 0 2.1.4 2.8 1.2l17.5 18.1c1.8 1.7 1.8 4.2.2 5.8z"},child:[]}]})(t)}function h(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M433 288.8c-7.7 0-14.3 5.9-14.9 13.6-6.9 83.1-76.8 147.9-161.8 147.9-89.5 0-162.4-72.4-162.4-161.4 0-87.6 70.6-159.2 158.2-161.4 2.3-.1 4.1 1.7 4.1 4v50.3c0 12.6 13.9 20.2 24.6 13.5L377 128c10-6.3 10-20.8 0-27.1l-96.1-66.4c-10.7-6.7-24.6.9-24.6 13.5v45.7c0 2.2-1.7 4-3.9 4C148 99.8 64 184.6 64 288.9 64 394.5 150.1 480 256.3 480c100.8 0 183.4-76.7 191.6-175.1.8-8.7-6.2-16.1-14.9-16.1z"},child:[]}]})(t)}function l(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M228.9 79.9L51.8 403.1C40.6 423.3 55.5 448 78.9 448h354.3c23.3 0 38.2-24.7 27.1-44.9L283.1 79.9c-11.7-21.2-42.5-21.2-54.2 0zM273.6 214L270 336h-28l-3.6-122h35.2zM256 402.4c-10.7 0-19.1-8.1-19.1-18.4s8.4-18.4 19.1-18.4 19.1 8.1 19.1 18.4-8.4 18.4-19.1 18.4z"},child:[]}]})(t)}function u(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M256 48C141.125 48 48 141.125 48 256s93.125 208 208 208 208-93.125 208-208S370.875 48 256 48zm107 229h-86v86h-42v-86h-86v-42h86v-86h42v86h86v42z"},child:[]}]})(t)}function e(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M416 277.333H277.333V416h-42.666V277.333H96v-42.666h138.667V96h42.666v138.667H416v42.666z"},child:[]}]})(t)}function d(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M256 48C140.559 48 48 140.559 48 256c0 115.436 92.559 208 208 208 115.435 0 208-92.564 208-208 0-115.441-92.564-208-208-208zm104.002 282.881l-29.12 29.117L256 285.117l-74.881 74.881-29.121-29.117L226.881 256l-74.883-74.881 29.121-29.116L256 226.881l74.881-74.878 29.12 29.116L285.119 256l74.883 74.881z"},child:[]}]})(t)}function g(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M405 136.798L375.202 107 256 226.202 136.798 107 107 136.798 226.202 256 107 375.202 136.798 405 256 285.798 375.202 405 405 375.202 285.798 256z"},child:[]}]})(t)}function o(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M256.1 144.8c56.2 0 101.9 45.3 101.9 101.1 0 13.1-2.6 25.5-7.3 37l59.5 59c30.8-25.5 55-58.4 69.9-96-35.3-88.7-122.3-151.6-224.2-151.6-28.5 0-55.8 5.1-81.1 14.1l44 43.7c11.6-4.6 24.1-7.3 37.3-7.3zM52.4 89.7l46.5 46.1 9.4 9.3c-33.9 26-60.4 60.8-76.3 100.8 35.2 88.7 122.2 151.6 224.1 151.6 31.6 0 61.7-6.1 89.2-17l8.6 8.5 59.7 59 25.9-25.7L78.2 64 52.4 89.7zM165 201.4l31.6 31.3c-1 4.2-1.6 8.7-1.6 13.1 0 33.5 27.3 60.6 61.1 60.6 4.5 0 9-.6 13.2-1.6l31.6 31.3c-13.6 6.7-28.7 10.7-44.8 10.7-56.2 0-101.9-45.3-101.9-101.1 0-15.8 4.1-30.7 10.8-44.3zm87.8-15.7l64.2 63.7.4-3.2c0-33.5-27.3-60.6-61.1-60.6l-3.5.1z"},child:[]}]})(t)}function v(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 512 512"},child:[{tag:"path",attr:{d:"M256 105c-101.8 0-188.4 62.4-224 151 35.6 88.6 122.2 151 224 151s188.4-62.4 224-151c-35.6-88.6-122.2-151-224-151zm0 251.7c-56 0-101.8-45.3-101.8-100.7S200 155.3 256 155.3 357.8 200.6 357.8 256 312 356.7 256 356.7zm0-161.1c-33.6 0-61.1 27.2-61.1 60.4s27.5 60.4 61.1 60.4 61.1-27.2 61.1-60.4-27.5-60.4-61.1-60.4z"},child:[]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/94730671-8100cd70fbbb3758.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[522],{84154:function(C,t,r){r.d(t,{$cL:function(){return n},sqC:function(){return H},zKx:function(){return c}});var V=r(46231);function n(C){return(0,V.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:[{tag:"path",attr:{d:"M17 17V19C17 19.5523 17.4477 20 18 20C18.5523 20 19 19.5523 19 19V4H5V15H3V3C3 2.44772 3.44772 2 4 2H20C20.5523 2 21 2.44772 21 3V19C21 20.6569 19.6569 22 18 22H4C2.34315 22 1 20.6569 1 19V17H17Z"},child:[]}]})(C)}function H(C){return(0,V.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:[{tag:"path",attr:{d:"M6 21.5C4.067 21.5 2.5 19.933 2.5 18C2.5 16.067 4.067 14.5 6 14.5C7.5852 14.5 8.92427 15.5539 9.35481 16.9992L15 16.9994V15L17 14.9994V9.24339L14.757 6.99938H9V9.00003H3V3.00003H9V4.99939H14.757L18 1.75739L22.2426 6.00003L19 9.24139V14.9994L21 15V21H15V18.9994L9.35499 19.0003C8.92464 20.4459 7.58543 21.5 6 21.5ZM6 16.5C5.17157 16.5 4.5 17.1716 4.5 18C4.5 18.8285 5.17157 19.5 6 19.5C6.82843 19.5 7.5 18.8285 7.5 18C7.5 17.1716 6.82843 16.5 6 16.5ZM19 17H17V19H19V17ZM18 4.58581L16.5858 6.00003L18 7.41424L19.4142 6.00003L18 4.58581ZM7 5.00003H5V7.00003H7V5.00003Z"},child:[]}]})(C)}function c(C){return(0,V.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:[{tag:"path",attr:{d:"M13.5 2C13.5 2.44425 13.3069 2.84339 13 3.11805V5H18C19.6569 5 21 6.34315 21 8V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V8C3 6.34315 4.34315 5 6 5H11V3.11805C10.6931 2.84339 10.5 2.44425 10.5 2C10.5 1.17157 11.1716 0.5 12 0.5C12.8284 0.5 13.5 1.17157 13.5 2ZM6 7C5.44772 7 5 7.44772 5 8V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V8C19 7.44772 18.5523 7 18 7H13H11H6ZM2 10H0V16H2V10ZM22 10H24V16H22V10ZM9 14.5C9.82843 14.5 10.5 13.8284 10.5 13C10.5 12.1716 9.82843 11.5 9 11.5C8.17157 11.5 7.5 12.1716 7.5 13C7.5 13.8284 8.17157 14.5 9 14.5ZM15 14.5C15.8284 14.5 16.5 13.8284 16.5 13C16.5 12.1716 15.8284 11.5 15 11.5C14.1716 11.5 13.5 12.1716 13.5 13C13.5 13.8284 14.1716 14.5 15 14.5Z"},child:[]}]})(C)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/app/_not-found/page-a7aa6e10be2a8ab3.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[409],{67589:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return n(83634)}])},83634:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}}),n(47043);let i=n(57437);n(2265);let o={fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},l={display:"inline-block"},r={display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},d={fontSize:14,fontWeight:400,lineHeight:"49px",margin:0};function s(){return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("title",{children:"404: This page could not be found."}),(0,i.jsx)("div",{style:o,children:(0,i.jsxs)("div",{children:[(0,i.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,i.jsx)("h1",{className:"next-error-h1",style:r,children:"404"}),(0,i.jsx)("div",{style:l,children:(0,i.jsx)("h2",{style:d,children:"This page could not be found."})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},function(e){e.O(0,[971,117,744],function(){return e(e.s=67589)}),_N_E=e.O()}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/c916193b-2488e101c53116b7.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[694],{76186:function(t,l,C){C.d(l,{$Wy:function(){return n},K46:function(){return i},ZWp:function(){return e}});var r=C(46231);function e(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none"},child:[{tag:"path",attr:{d:"M10 11C10 10.4477 10.4477 10 11 10H13C13.5523 10 14 10.4477 14 11C14 11.5523 13.5523 12 13 12H11C10.4477 12 10 11.5523 10 11Z",fill:"currentColor"},child:[]},{tag:"path",attr:{d:"M11 14C10.4477 14 10 14.4477 10 15C10 15.5523 10.4477 16 11 16H13C13.5523 16 14 15.5523 14 15C14 14.4477 13.5523 14 13 14H11Z",fill:"currentColor"},child:[]},{tag:"path",attr:{fillRule:"evenodd",clipRule:"evenodd",d:"M9.09447 4.74918C8.41606 4.03243 8 3.0648 8 2H10C10 3.10457 10.8954 4 12 4C13.1046 4 14 3.10457 14 2H16C16 3.0648 15.5839 4.03243 14.9055 4.74918C16.1782 5.45491 17.1673 6.6099 17.6586 8H19C19.5523 8 20 8.44772 20 9C20 9.55229 19.5523 10 19 10H18V12H19C19.5523 12 20 12.4477 20 13C20 13.5523 19.5523 14 19 14H18V16H19C19.5523 16 20 16.4477 20 17C20 17.5523 19.5523 18 19 18H17.6586C16.8349 20.3304 14.6124 22 12 22C9.38756 22 7.16508 20.3304 6.34141 18H5C4.44772 18 4 17.5523 4 17C4 16.4477 4.44772 16 5 16H6V14H5C4.44772 14 4 13.5523 4 13C4 12.4477 4.44772 12 5 12H6V10H5C4.44772 10 4 9.55229 4 9C4 8.44772 4.44772 8 5 8H6.34141C6.83274 6.6099 7.82181 5.45491 9.09447 4.74918ZM8 16V10C8 7.79086 9.79086 6 12 6C14.2091 6 16 7.79086 16 10V16C16 18.2091 14.2091 20 12 20C9.79086 20 8 18.2091 8 16Z",fill:"currentColor"},child:[]}]})(t)}function i(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none"},child:[{tag:"path",attr:{d:"M7 18H17V16H7V18Z",fill:"currentColor"},child:[]},{tag:"path",attr:{d:"M17 14H7V12H17V14Z",fill:"currentColor"},child:[]},{tag:"path",attr:{d:"M7 10H11V8H7V10Z",fill:"currentColor"},child:[]},{tag:"path",attr:{fillRule:"evenodd",clipRule:"evenodd",d:"M6 2C4.34315 2 3 3.34315 3 5V19C3 20.6569 4.34315 22 6 22H18C19.6569 22 21 20.6569 21 19V9C21 5.13401 17.866 2 14 2H6ZM6 4H13V9H19V19C19 19.5523 18.5523 20 18 20H6C5.44772 20 5 19.5523 5 19V5C5 4.44772 5.44772 4 6 4ZM15 4.10002C16.6113 4.4271 17.9413 5.52906 18.584 7H15V4.10002Z",fill:"currentColor"},child:[]}]})(t)}function n(t){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none"},child:[{tag:"path",attr:{fillRule:"evenodd",clipRule:"evenodd",d:"M14 7C13.4477 7 13 7.44772 13 8V16C13 16.5523 13.4477 17 14 17H18C18.5523 17 19 16.5523 19 16V8C19 7.44772 18.5523 7 18 7H14ZM17 9H15V15H17V9Z",fill:"currentColor"},child:[]},{tag:"path",attr:{d:"M6 7C5.44772 7 5 7.44772 5 8C5 8.55228 5.44772 9 6 9H10C10.5523 9 11 8.55228 11 8C11 7.44772 10.5523 7 10 7H6Z",fill:"currentColor"},child:[]},{tag:"path",attr:{d:"M6 11C5.44772 11 5 11.4477 5 12C5 12.5523 5.44772 13 6 13H10C10.5523 13 11 12.5523 11 12C11 11.4477 10.5523 11 10 11H6Z",fill:"currentColor"},child:[]},{tag:"path",attr:{d:"M5 16C5 15.4477 5.44772 15 6 15H10C10.5523 15 11 15.4477 11 16C11 16.5523 10.5523 17 10 17H6C5.44772 17 5 16.5523 5 16Z",fill:"currentColor"},child:[]},{tag:"path",attr:{fillRule:"evenodd",clipRule:"evenodd",d:"M4 3C2.34315 3 1 4.34315 1 6V18C1 19.6569 2.34315 21 4 21H20C21.6569 21 23 19.6569 23 18V6C23 4.34315 21.6569 3 20 3H4ZM20 5H4C3.44772 5 3 5.44772 3 6V18C3 18.5523 3.44772 19 4 19H20C20.5523 19 21 18.5523 21 18V6C21 5.44772 20.5523 5 20 5Z",fill:"currentColor"},child:[]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/e34aaff9-0dee46ea085fd2ca.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[994],{67776:function(t,a,h){h.d(a,{kkL:function(){return d}});var i=h(46231);function d(t){return(0,i.w_)({tag:"svg",attr:{viewBox:"0 0 24 24"},child:[{tag:"g",attr:{id:"Text_Align_Justify"},child:[{tag:"g",attr:{},child:[{tag:"path",attr:{d:"M20.437,4.064H3.563a.5.5,0,0,1,0-1H20.437a.5.5,0,0,1,0,1Z"},child:[]},{tag:"path",attr:{d:"M20.437,8.5H3.563a.5.5,0,0,1,0-1H20.437a.5.5,0,0,1,0,1Z"},child:[]},{tag:"path",attr:{d:"M20.437,16.5H3.563a.5.5,0,1,1,0-1H20.437a.5.5,0,0,1,0,1Z"},child:[]},{tag:"path",attr:{d:"M20.437,12.5H3.563a.5.5,0,0,1,0-1H20.437a.5.5,0,0,1,0,1Z"},child:[]},{tag:"path",attr:{d:"M20.437,20.936H3.563a.5.5,0,1,1,0-1H20.437a.5.5,0,1,1,0,1Z"},child:[]}]}]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/eec3d76d-069cf1d79aaf4965.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[305],{85130:function(t,a,c){c.d(a,{MSG:function(){return l},QC3:function(){return r},_aN:function(){return h},pPp:function(){return i}});var n=c(46231);function r(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24"},child:[{tag:"path",attr:{d:"M12 1.25c2.487 0 4.773.402 6.466 1.079.844.337 1.577.758 2.112 1.264.536.507.922 1.151.922 1.907v12.987l-.026.013h.026c0 .756-.386 1.4-.922 1.907-.535.506-1.268.927-2.112 1.264-1.693.677-3.979 1.079-6.466 1.079s-4.774-.402-6.466-1.079c-.844-.337-1.577-.758-2.112-1.264C2.886 19.9 2.5 19.256 2.5 18.5h.026l-.026-.013V5.5c0-.756.386-1.4.922-1.907.535-.506 1.268-.927 2.112-1.264C7.226 1.652 9.513 1.25 12 1.25ZM4 14.371v4.116l-.013.013H4c0 .211.103.487.453.817.351.332.898.666 1.638.962 1.475.589 3.564.971 5.909.971 2.345 0 4.434-.381 5.909-.971.739-.296 1.288-.63 1.638-.962.349-.33.453-.607.453-.817h.013L20 18.487v-4.116a7.85 7.85 0 0 1-1.534.8c-1.693.677-3.979 1.079-6.466 1.079s-4.774-.402-6.466-1.079a7.843 7.843 0 0 1-1.534-.8ZM20 12V7.871a7.85 7.85 0 0 1-1.534.8C16.773 9.348 14.487 9.75 12 9.75s-4.774-.402-6.466-1.079A7.85 7.85 0 0 1 4 7.871V12c0 .21.104.487.453.817.35.332.899.666 1.638.961 1.475.59 3.564.972 5.909.972 2.345 0 4.434-.382 5.909-.972.74-.295 1.287-.629 1.638-.96.35-.33.453-.607.453-.818ZM4 5.5c0 .211.103.487.453.817.351.332.898.666 1.638.962 1.475.589 3.564.971 5.909.971 2.345 0 4.434-.381 5.909-.971.739-.296 1.288-.63 1.638-.962.349-.33.453-.607.453-.817 0-.211-.103-.487-.453-.817-.351-.332-.898-.666-1.638-.962-1.475-.589-3.564-.971-5.909-.971-2.345 0-4.434.381-5.909.971-.739.296-1.288.63-1.638.962C4.104 5.013 4 5.29 4 5.5Z"},child:[]}]})(t)}function h(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24"},child:[{tag:"path",attr:{d:"M17.28 9.28a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5Z"},child:[]},{tag:"path",attr:{d:"M12 1c6.075 0 11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12 5.925 1 12 1ZM2.5 12a9.5 9.5 0 0 0 9.5 9.5 9.5 9.5 0 0 0 9.5-9.5A9.5 9.5 0 0 0 12 2.5 9.5 9.5 0 0 0 2.5 12Z"},child:[]}]})(t)}function i(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24"},child:[{tag:"path",attr:{d:"M12 1c6.075 0 11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12 5.925 1 12 1ZM2.5 12a9.5 9.5 0 0 0 9.5 9.5 9.5 9.5 0 0 0 9.5-9.5A9.5 9.5 0 0 0 12 2.5 9.5 9.5 0 0 0 2.5 12Zm9.5 2a2 2 0 1 1-.001-3.999A2 2 0 0 1 12 14Z"},child:[]}]})(t)}function l(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24"},child:[{tag:"path",attr:{d:"M16 1.75V3h5.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H8V1.75C8 .784 8.784 0 9.75 0h4.5C15.216 0 16 .784 16 1.75Zm-6.5 0V3h5V1.75a.25.25 0 0 0-.25-.25h-4.5a.25.25 0 0 0-.25.25ZM4.997 6.178a.75.75 0 1 0-1.493.144L4.916 20.92a1.75 1.75 0 0 0 1.742 1.58h10.684a1.75 1.75 0 0 0 1.742-1.581l1.413-14.597a.75.75 0 0 0-1.494-.144l-1.412 14.596a.25.25 0 0 1-.249.226H6.658a.25.25 0 0 1-.249-.226L4.997 6.178Z"},child:[]},{tag:"path",attr:{d:"M9.206 7.501a.75.75 0 0 1 .793.705l.5 8.5A.75.75 0 1 1 9 16.794l-.5-8.5a.75.75 0 0 1 .705-.793Zm6.293.793A.75.75 0 1 0 14 8.206l-.5 8.5a.75.75 0 0 0 1.498.088l.5-8.5Z"},child:[]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/f7333993-a8b5748c01dd26c0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[516],{37780:function(a,l,t){t.d(l,{drw:function(){return n}});var r=t(46231);function n(a){return(0,r.w_)({tag:"svg",attr:{viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},child:[{tag:"path",attr:{d:"M15.98 1.804a1 1 0 0 0-1.96 0l-.24 1.192a1 1 0 0 1-.784.785l-1.192.238a1 1 0 0 0 0 1.962l1.192.238a1 1 0 0 1 .785.785l.238 1.192a1 1 0 0 0 1.962 0l.238-1.192a1 1 0 0 1 .785-.785l1.192-.238a1 1 0 0 0 0-1.962l-1.192-.238a1 1 0 0 1-.785-.785l-.238-1.192ZM6.949 5.684a1 1 0 0 0-1.898 0l-.683 2.051a1 1 0 0 1-.633.633l-2.051.683a1 1 0 0 0 0 1.898l2.051.684a1 1 0 0 1 .633.632l.683 2.051a1 1 0 0 0 1.898 0l.683-2.051a1 1 0 0 1 .633-.633l2.051-.683a1 1 0 0 0 0-1.898l-2.051-.683a1 1 0 0 1-.633-.633L6.95 5.684ZM13.949 13.684a1 1 0 0 0-1.898 0l-.184.551a1 1 0 0 1-.632.633l-.551.183a1 1 0 0 0 0 1.898l.551.183a1 1 0 0 1 .633.633l.183.551a1 1 0 0 0 1.898 0l.184-.551a1 1 0 0 1 .632-.633l.551-.183a1 1 0 0 0 0-1.898l-.551-.184a1 1 0 0 1-.633-.632l-.183-.551Z"},child:[]}]})(a)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/f8025e75-f62f273c531a4aaa.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[950],{45168:function(t,a,r){r.d(a,{AdG:function(){return h},eJi:function(){return o},igS:function(){return c},mgB:function(){return d},owl:function(){return i},sDK:function(){return l}});var n=r(46231);function h(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:[{tag:"path",attr:{d:"M19 3a3 3 0 0 1 1 5.829v1.171a3 3 0 0 1 -3 3h-4v2.171a3.001 3.001 0 1 1 -4 2.829l.005 -.176a3 3 0 0 1 1.995 -2.654v-2.17h-5v2.171a3.001 3.001 0 1 1 -4 2.829l.005 -.176a3 3 0 0 1 1.995 -2.654v-6.341a3 3 0 0 1 -2 -2.829l.005 -.176a3 3 0 1 1 3.996 3.005l-.001 2.171h5v-2.17a3 3 0 0 1 -2 -2.83l.005 -.176a3 3 0 1 1 3.996 3.005l-.001 2.171h4a1 1 0 0 0 1 -1v-1.17a3 3 0 0 1 -2 -2.83l.005 -.176a3 3 0 0 1 2.995 -2.824"},child:[]}]})(t)}function i(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M9 14l-4 -4l4 -4"},child:[]},{tag:"path",attr:{d:"M5 10h11a4 4 0 1 1 0 8h-1"},child:[]}]})(t)}function o(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M7 21l4 -12m2 0l1.48 4.439m.949 2.847l1.571 4.714"},child:[]},{tag:"path",attr:{d:"M12 7m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"},child:[]},{tag:"path",attr:{d:"M4 12c1.526 2.955 4.588 5 8 5c3.41 0 6.473 -2.048 8 -5"},child:[]},{tag:"path",attr:{d:"M12 5v-2"},child:[]}]})(t)}function d(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M12 21l-8 -4.5v-9l8 -4.5l8 4.5v4.5"},child:[]},{tag:"path",attr:{d:"M12 12l8 -4.5"},child:[]},{tag:"path",attr:{d:"M12 12v9"},child:[]},{tag:"path",attr:{d:"M12 12l-8 -4.5"},child:[]},{tag:"path",attr:{d:"M22 18h-7"},child:[]},{tag:"path",attr:{d:"M18 15l-3 3l3 3"},child:[]}]})(t)}function c(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M12 19.875c0 .621 -.512 1.125 -1.143 1.125h-5.714a1.134 1.134 0 0 1 -1.143 -1.125v-15.875a1 1 0 0 1 1 -1h5.857c.631 0 1.143 .504 1.143 1.125z"},child:[]},{tag:"path",attr:{d:"M12 9h-2"},child:[]},{tag:"path",attr:{d:"M12 6h-3"},child:[]},{tag:"path",attr:{d:"M12 12h-3"},child:[]},{tag:"path",attr:{d:"M12 18h-3"},child:[]},{tag:"path",attr:{d:"M12 15h-2"},child:[]},{tag:"path",attr:{d:"M21 3h-4"},child:[]},{tag:"path",attr:{d:"M19 3v18"},child:[]},{tag:"path",attr:{d:"M21 21h-4"},child:[]}]})(t)}function l(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:[{tag:"path",attr:{d:"M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"},child:[]},{tag:"path",attr:{d:"M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"},child:[]}]})(t)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/f97e080b-aae770272181a2bd.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[665],{76417:function(a,t,r){r.d(t,{GO8:function(){return u},MZe:function(){return c},iN8:function(){return i},y$S:function(){return n}});var l=r(46231);function n(a){return(0,l.w_)({tag:"svg",attr:{viewBox:"0 0 256 256",fill:"currentColor"},child:[{tag:"path",attr:{d:"M248,152a8,8,0,0,1-8,8H224v16a8,8,0,0,1-16,0V160H192a8,8,0,0,1,0-16h16V128a8,8,0,0,1,16,0v16h16A8,8,0,0,1,248,152ZM56,72H72V88a8,8,0,0,0,16,0V72h16a8,8,0,0,0,0-16H88V40a8,8,0,0,0-16,0V56H56a8,8,0,0,0,0,16ZM184,192h-8v-8a8,8,0,0,0-16,0v8h-8a8,8,0,0,0,0,16h8v8a8,8,0,0,0,16,0v-8h8a8,8,0,0,0,0-16ZM219.31,80,80,219.31a16,16,0,0,1-22.62,0L36.68,198.63a16,16,0,0,1,0-22.63L176,36.69a16,16,0,0,1,22.63,0l20.68,20.68A16,16,0,0,1,219.31,80ZM208,68.69,187.31,48l-32,32L176,100.69Z"},child:[]}]})(a)}function i(a){return(0,l.w_)({tag:"svg",attr:{viewBox:"0 0 256 256",fill:"currentColor"},child:[{tag:"path",attr:{d:"M237.66,141.66l-32,32A8,8,0,0,1,192,168V144H123.31l-40,40,18.35,18.34A8,8,0,0,1,96,216H48a8,8,0,0,1-8-8V160a8,8,0,0,1,13.66-5.66L72,172.69l40-40V64H88a8,8,0,0,1-5.66-13.66l32-32a8,8,0,0,1,11.32,0l32,32A8,8,0,0,1,152,64H128v64h64V104a8,8,0,0,1,13.66-5.66l32,32A8,8,0,0,1,237.66,141.66Z"},child:[]}]})(a)}function u(a){return(0,l.w_)({tag:"svg",attr:{viewBox:"0 0 256 256",fill:"currentColor"},child:[{tag:"path",attr:{d:"M75.19,198.4a8,8,0,0,0,11.21-1.6,52,52,0,0,1,83.2,0,8,8,0,1,0,12.8-9.6A67.88,67.88,0,0,0,155,165.51a40,40,0,1,0-53.94,0A67.88,67.88,0,0,0,73.6,187.2,8,8,0,0,0,75.19,198.4ZM128,112a24,24,0,1,1-24,24A24,24,0,0,1,128,112Zm72-88H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V40A16,16,0,0,0,200,24Zm0,192H56V40H200ZM88,64a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,64Z"},child:[]}]})(a)}function c(a){return(0,l.w_)({tag:"svg",attr:{viewBox:"0 0 256 256",fill:"currentColor"},child:[{tag:"path",attr:{d:"M224,128a8,8,0,0,1-8,8H104a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM104,72H216a8,8,0,0,0,0-16H104a8,8,0,0,0,0,16ZM216,184H104a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM43.58,55.16,48,52.94V104a8,8,0,0,0,16,0V40a8,8,0,0,0-11.58-7.16l-16,8a8,8,0,0,0,7.16,14.32ZM79.77,156.72a23.73,23.73,0,0,0-9.6-15.95,24.86,24.86,0,0,0-34.11,4.7,23.63,23.63,0,0,0-3.57,6.46,8,8,0,1,0,15,5.47,7.84,7.84,0,0,1,1.18-2.13,8.76,8.76,0,0,1,12-1.59A7.91,7.91,0,0,1,63.93,159a7.64,7.64,0,0,1-1.57,5.78,1,1,0,0,0-.08.11L33.59,203.21A8,8,0,0,0,40,216H72a8,8,0,0,0,0-16H56l19.08-25.53A23.47,23.47,0,0,0,79.77,156.72Z"},child:[]}]})(a)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/fca4dd8b-1b5ad2d023949d77.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[376],{19956:function(c,t,n){n.d(t,{Ks7:function(){return r},eBn:function(){return a}});var h=n(46231);function r(c){return(0,h.w_)({tag:"svg",attr:{viewBox:"0 0 1024 1024"},child:[{tag:"path",attr:{d:"M388.8 896.4v-27.198c.6-2.2 1.6-4.2 2-6.4 8.8-57.2 56.4-102.4 112.199-106.2 62.4-4.4 115.2 31.199 132.4 89.199 2.2 7.6 3.8 15.6 5.8 23.4v27.2c-.6 1.8-1.6 3.399-1.8 5.399-8.6 52.8-46.6 93-98.6 104.4-4 .8-8 2-12 3h-27.2c-1.8-.6-3.6-1.6-5.4-1.8-52-8.4-91.599-45.4-103.6-96.8-1.2-5-2.6-9.6-3.8-14.2zm252.4-768.797l-.001 27.202c-.6 2.2-1.6 4.2-1.8 6.4-9 57.6-56.8 102.6-113.2 106.2-62.2 4-114.8-32-131.8-90.2-2.2-7.401-3.8-15-5.6-22.401v-27.2c.6-1.8 1.6-3.4 2-5.2 9.6-52 39.8-86 90.2-102.2 6.6-2.2 13.6-3.4 20.4-5.2h27.2c1.8.6 3.6 1.6 5.4 1.8 52.2 8.6 91.6 45.4 103.6 96.8 1.201 4.8 2.401 9.4 3.601 13.999zm-.001 370.801v27.2c-.6 2.2-1.6 4.2-2 6.4-9 57.4-58.6 103.6-114.6 106-63 2.8-116.4-35.2-131.4-93.8-1.6-6.2-3-12.4-4.4-18.6v-27.2c.6-2.2 1.6-4.2 2-6.4 8.8-57.4 58.6-103.601 114.6-106.2 63-3 116.4 35.2 131.4 93.8 1.6 6.4 3 12.6 4.4 18.8z"},child:[]}]})(c)}function a(c){return(0,h.w_)({tag:"svg",attr:{viewBox:"0 0 1024 1024"},child:[{tag:"path",attr:{d:"M899.4 638.2h-27.198c-2.2-.6-4.2-1.6-6.4-2-57.2-8.8-102.4-56.4-106.2-112.199-4.401-62.4 31.199-115.2 89.199-132.4 7.6-2.2 15.6-3.8 23.399-5.8h27.2c1.8.6 3.4 1.6 5.4 1.8 52.8 8.6 93 46.6 104.4 98.6.8 4 2 8 3 12v27.2c-.6 1.8-1.6 3.6-1.8 5.4-8.4 52-45.4 91.599-96.801 103.6-5 1.2-9.6 2.6-14.2 3.8zM130.603 385.8l27.202.001c2.2.6 4.2 1.6 6.4 1.8 57.6 9 102.6 56.8 106.2 113.2 4 62.2-32 114.8-90.2 131.8-7.401 2.2-15 3.8-22.401 5.6h-27.2c-1.8-.6-3.4-1.6-5.2-2-52-9.6-86-39.8-102.2-90.2-2.2-6.6-3.4-13.6-5.2-20.4v-27.2c.6-1.8 1.6-3.6 1.8-5.4 8.6-52.2 45.4-91.6 96.8-103.6 4.8-1.201 9.4-2.401 13.999-3.601zm370.801.001h27.2c2.2.6 4.2 1.6 6.4 2 57.4 9 103.6 58.6 106 114.6 2.8 63-35.2 116.4-93.8 131.4-6.2 1.6-12.4 3-18.6 4.4h-27.2c-2.2-.6-4.2-1.6-6.4-2-57.4-8.8-103.601-58.6-106.2-114.6-3-63 35.2-116.4 93.8-131.4 6.4-1.6 12.6-3 18.8-4.4z"},child:[]}]})(c)}}}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/main-app-edae924f4ed22c2f.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[744],{36551:function(e,n,t){Promise.resolve().then(t.t.bind(t,12846,23)),Promise.resolve().then(t.t.bind(t,19107,23)),Promise.resolve().then(t.t.bind(t,61060,23)),Promise.resolve().then(t.t.bind(t,4707,23)),Promise.resolve().then(t.t.bind(t,80,23)),Promise.resolve().then(t.t.bind(t,36423,23))}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[971,117],function(){return n(54278),n(36551)}),_N_E=e.O()}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/pages/_app-15e2daefa259f0b5.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{41597:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return u(48141)}])}},function(n){var _=function(_){return n(n.s=_)};n.O(0,[774,179],function(){return _(41597),_(37253)}),_N_E=n.O()}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/pages/_error-28b803cb2479b966.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{81981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(18529)}])}},function(n){n.O(0,[888,774,179],function(){return n(n.s=81981)}),_N_E=n.O()}]); -------------------------------------------------------------------------------- /elysia/api/static/_next/static/chunks/webpack-bcddd50ab1ee801f.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /elysia/api/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /elysia/api/user_configs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/user_configs/__init__.py -------------------------------------------------------------------------------- /elysia/api/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/api/utils/__init__.py -------------------------------------------------------------------------------- /elysia/api/utils/default_payloads.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | 4 | def error_payload(text: str, conversation_id: str, query_id: str): 5 | return { 6 | "type": "error", 7 | "id": f"err-{str(uuid.uuid4())}", 8 | "conversation_id": conversation_id, 9 | "query_id": query_id, 10 | "payload": {"text": text}, 11 | } 12 | -------------------------------------------------------------------------------- /elysia/api/utils/encryption.py: -------------------------------------------------------------------------------- 1 | import os 2 | from cryptography.fernet import Fernet 3 | from dotenv import set_key, load_dotenv 4 | from copy import deepcopy 5 | 6 | 7 | def encrypt_api_keys(settings_dict: dict): 8 | 9 | load_dotenv() 10 | 11 | settings_dict_copy = deepcopy(settings_dict) 12 | 13 | # Check if an auth key is set in the environment variables 14 | if "FERNET_KEY" in os.environ: 15 | auth_key = os.environ["FERNET_KEY"] 16 | else: # create one 17 | auth_key = Fernet.generate_key().decode("utf-8") 18 | set_key(".env", "FERNET_KEY", auth_key) 19 | 20 | # Encode all api keys 21 | f = Fernet(auth_key.encode("utf-8")) 22 | if "API_KEYS" in settings_dict_copy: 23 | for key in settings_dict_copy["API_KEYS"]: 24 | if key != "null": 25 | settings_dict_copy["API_KEYS"][key] = f.encrypt( 26 | settings_dict_copy["API_KEYS"][key].encode("utf-8") 27 | ).decode("utf-8") 28 | 29 | if "WCD_API_KEY" in settings_dict_copy: 30 | settings_dict_copy["WCD_API_KEY"] = f.encrypt( 31 | settings_dict_copy["WCD_API_KEY"].encode("utf-8") 32 | ).decode("utf-8") 33 | 34 | return settings_dict_copy 35 | 36 | 37 | def decrypt_api_keys(settings_dict: dict): 38 | 39 | load_dotenv() 40 | 41 | settings_dict_copy = deepcopy(settings_dict) 42 | 43 | if "FERNET_KEY" in os.environ: 44 | auth_key = os.environ["FERNET_KEY"] 45 | else: # create one 46 | auth_key = Fernet.generate_key().decode("utf-8") 47 | set_key(".env", "FERNET_KEY", auth_key) 48 | 49 | # decode all api keys 50 | f = Fernet(auth_key.encode("utf-8")) 51 | 52 | if "API_KEYS" in settings_dict_copy: 53 | for key in settings_dict_copy["API_KEYS"]: 54 | if key != "null": 55 | settings_dict_copy["API_KEYS"][key] = f.decrypt( 56 | settings_dict_copy["API_KEYS"][key].encode("utf-8") 57 | ).decode("utf-8") 58 | 59 | if "WCD_API_KEY" in settings_dict_copy: 60 | settings_dict_copy["WCD_API_KEY"] = f.decrypt( 61 | settings_dict_copy["WCD_API_KEY"].encode("utf-8") 62 | ).decode("utf-8") 63 | 64 | return settings_dict_copy 65 | -------------------------------------------------------------------------------- /elysia/api/utils/models.py: -------------------------------------------------------------------------------- 1 | models = { 2 | "openai": { 3 | "gpt-4.1": { 4 | "name": "gpt-4.1", 5 | "api_keys": ["openai_api_key"], 6 | "speed": "slow", 7 | "accuracy": "high", 8 | }, 9 | "gpt-4.1-mini": { 10 | "name": "gpt-4.1-mini", 11 | "api_keys": ["openai_api_key"], 12 | "speed": "medium", 13 | "accuracy": "medium", 14 | }, 15 | "gpt-4.1-nano": { 16 | "name": "gpt-4.1-nano", 17 | "api_keys": ["openai_api_key"], 18 | "speed": "fast", 19 | "accuracy": "low", 20 | }, 21 | }, 22 | "anthropic": { 23 | "claude-sonnet-4-20250514": { 24 | "name": "claude-sonnet-4-20250514", 25 | "api_keys": ["anthropic_api_key"], 26 | "speed": "slow", 27 | "accuracy": "high", 28 | }, 29 | "claude-3-7-sonnet-20250219": { 30 | "name": "claude-3-7-sonnet-20250219", 31 | "api_keys": ["anthropic_api_key"], 32 | "speed": "slow", 33 | "accuracy": "high", 34 | }, 35 | "claude-3-5-haiku-20241022": { 36 | "name": "claude-3-5-haiku-20241022", 37 | "api_keys": ["anthropic_api_key"], 38 | "speed": "fast", 39 | "accuracy": "low", 40 | }, 41 | }, 42 | "openrouter/openai": { 43 | "gpt-4.1": { 44 | "name": "gpt-4.1", 45 | "api_keys": ["openai_api_key"], 46 | "speed": "slow", 47 | "accuracy": "high", 48 | }, 49 | "gpt-4.1-mini": { 50 | "name": "gpt-4.1-mini", 51 | "api_keys": ["openai_api_key"], 52 | "speed": "medium", 53 | "accuracy": "medium", 54 | }, 55 | "gpt-4.1-nano": { 56 | "name": "gpt-4.1-nano", 57 | "api_keys": ["openai_api_key"], 58 | "speed": "fast", 59 | "accuracy": "low", 60 | }, 61 | }, 62 | "openrouter/anthropic": { 63 | "claude-sonnet-4": { 64 | "name": "claude-sonnet-4", 65 | "api_keys": ["openrouter_api_key"], 66 | "speed": "slow", 67 | "accuracy": "high", 68 | }, 69 | "claude-3-7-sonnet": { 70 | "name": "claude-3-7-sonnet", 71 | "api_keys": ["openrouter_api_key"], 72 | "speed": "slow", 73 | "accuracy": "high", 74 | }, 75 | "claude-3-5-haiku": { 76 | "name": "claude-3-5-haiku", 77 | "api_keys": ["openrouter_api_key"], 78 | "speed": "fast", 79 | "accuracy": "low", 80 | }, 81 | }, 82 | "openrouter/google": { 83 | "gemini-2.5-flash": { 84 | "name": "gemini-2.5-flash", 85 | "api_keys": ["openrouter_api_key"], 86 | "speed": "fast", 87 | "accuracy": "medium", 88 | }, 89 | "gemini-2.5-flash-lite": { 90 | "name": "gemini-2.5-flash-lite", 91 | "api_keys": ["openrouter_api_key"], 92 | "speed": "fast", 93 | "accuracy": "low", 94 | }, 95 | "gemini-2.0-flash-001": { 96 | "name": "gemini-2.0-flash-001", 97 | "api_keys": ["openrouter_api_key"], 98 | "speed": "fast", 99 | "accuracy": "medium", 100 | }, 101 | "gemini-2.5-pro": { 102 | "name": "gemini-2.5-pro", 103 | "api_keys": ["openrouter_api_key"], 104 | "speed": "slow", 105 | "accuracy": "high", 106 | }, 107 | }, 108 | "gemini": { 109 | "gemini-2.5-flash": { 110 | "name": "gemini-2.5-flash", 111 | "api_keys": ["gemini_api_key"], 112 | "speed": "fast", 113 | "accuracy": "medium", 114 | }, 115 | "gemini-2.5-flash-lite": { 116 | "name": "gemini-2.5-flash-lite", 117 | "api_keys": ["gemini_api_key"], 118 | "speed": "fast", 119 | "accuracy": "low", 120 | }, 121 | "gemini-2.0-flash-001": { 122 | "name": "gemini-2.0-flash-001", 123 | "api_keys": ["gemini_api_key"], 124 | "speed": "fast", 125 | "accuracy": "medium", 126 | }, 127 | "gemini-2.5-pro": { 128 | "name": "gemini-2.5-pro", 129 | "api_keys": ["gemini_api_key"], 130 | "speed": "slow", 131 | "accuracy": "high", 132 | }, 133 | }, 134 | } 135 | -------------------------------------------------------------------------------- /elysia/api/utils/ner.py: -------------------------------------------------------------------------------- 1 | from elysia.config import nlp 2 | 3 | 4 | def named_entity_recognition(text: str): 5 | """ 6 | Performs Named Entity Recognition using spaCy. 7 | Returns a list of entities with their labels, start and end positions. 8 | """ 9 | try: 10 | doc = nlp(text) 11 | out = {"text": text, "entity_spans": [], "noun_spans": [], "error": ""} 12 | 13 | for ent in doc.ents: 14 | out["entity_spans"].append((ent.start_char, ent.end_char)) 15 | 16 | # Get noun spans 17 | for token in doc: 18 | if token.pos_ == "NOUN": 19 | span = doc[token.i : token.i + 1] 20 | out["noun_spans"].append((span.start_char, span.end_char)) 21 | 22 | return out 23 | 24 | except Exception as e: 25 | return { 26 | "text": text, 27 | "entity_spans": [], 28 | "noun_spans": [], 29 | "error": str(e), 30 | } 31 | -------------------------------------------------------------------------------- /elysia/api/utils/resources.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | 3 | from elysia.api.services.user import UserManager 4 | 5 | 6 | def get_total_memory(): 7 | return round(psutil.virtual_memory().total / (1024 * 1024), 2) 8 | 9 | 10 | def get_free_memory(): 11 | return round(psutil.virtual_memory().available / (1024 * 1024), 2) 12 | 13 | 14 | def get_cpu_usage(): 15 | return psutil.cpu_percent(interval=1) 16 | 17 | 18 | def get_memory_usage(): 19 | return psutil.virtual_memory().percent 20 | 21 | 22 | def get_disk_usage(): 23 | return psutil.disk_usage("/").percent 24 | 25 | 26 | def get_number_local_users(user_manager: UserManager): 27 | return len(user_manager.users) 28 | 29 | 30 | async def get_average_user_memory(user_manager: UserManager): 31 | if len(user_manager.users) == 0: 32 | return 0, 0 33 | avg_user_memory = 0 34 | avg_tree_memory = 0 35 | for user in user_manager.users.values(): 36 | user_memory = 0 37 | for tree in user["tree_manager"].trees.values(): 38 | user_memory += tree["tree"].detailed_memory_usage()["total"] / (1024 * 1024) 39 | 40 | if len(user["tree_manager"].trees) > 0: 41 | avg_tree_memory += user_memory / len(user["tree_manager"].trees) 42 | 43 | avg_user_memory += user_memory 44 | 45 | return ( 46 | round(avg_user_memory / len(user_manager.users), 2), 47 | round(avg_tree_memory / len(user_manager.users), 2), 48 | ) 49 | 50 | 51 | # async def get_average_user_requests(user_manager: UserManager): 52 | # return await user_manager.get_average_user_requests() 53 | 54 | 55 | async def print_resources( 56 | user_manager: UserManager | None = None, save_to_file: bool = False 57 | ): 58 | if user_manager is not None: 59 | avg_user_memory, avg_tree_memory = await get_average_user_memory(user_manager) 60 | # avg_user_requests = await get_average_user_requests(user_manager) 61 | # num_users_db = await get_number_local_users_db(user_manager) 62 | 63 | print("\n\n\n\n") 64 | print("-" * 100) 65 | print(f"ELYSIA RESOURCES") 66 | print("-" * 100) 67 | print(f"Total memory: {get_total_memory()} MB") 68 | print(f"Free memory: {get_free_memory()} MB") 69 | print(f"CPU usage: {get_cpu_usage()}%") 70 | print(f"Memory usage: {get_memory_usage()}%") 71 | print(f"Disk usage: {get_disk_usage()}%") 72 | if user_manager is not None: 73 | print("\n\n") 74 | print("-" * 100) 75 | print(f"USER STATISTICS") 76 | print("-" * 100) 77 | print(f"Number of local users: {get_number_local_users(user_manager)}") 78 | # print(f"Number of unique users in database: {num_users_db}") 79 | print(f"Average user memory usage: {avg_user_memory} MB") 80 | print(f"Average tree memory usage: {avg_tree_memory} MB") 81 | # print(f"Average user requests: {avg_user_requests}") 82 | print("-" * 100) 83 | print("\n\n\n\n") 84 | 85 | if save_to_file: 86 | with open("resources.txt", "w") as f: 87 | f.write("-" * 100) 88 | f.write("\n") 89 | f.write(f"ELYSIA RESOURCES\n") 90 | f.write("-" * 100) 91 | f.write("\n") 92 | f.write(f"Total memory: {get_total_memory()} MB\n") 93 | f.write(f"Free memory: {get_free_memory()} MB\n") 94 | f.write(f"CPU usage: {get_cpu_usage()}%\n") 95 | f.write(f"Memory usage: {get_memory_usage()}%\n") 96 | f.write(f"Disk usage: {get_disk_usage()}%\n") 97 | if user_manager is not None: 98 | f.write("\n\n") 99 | f.write("-" * 100) 100 | f.write("\nUSER STATISTICS\n") 101 | f.write("-" * 100) 102 | f.write("\n\n") 103 | f.write( 104 | f"Number of local users: {get_number_local_users(user_manager)}\n" 105 | ) 106 | # f.write(f"Number of unique users in database: {num_users_db}\n") 107 | f.write(f"Average user memory usage: {avg_user_memory} MB\n") 108 | f.write(f"Average tree memory usage: {avg_tree_memory} MB\n") 109 | # f.write(f"Average user requests: {avg_user_requests}\n") 110 | 111 | 112 | # if __name__ == "__main__": 113 | # await print_resources() 114 | -------------------------------------------------------------------------------- /elysia/api/utils/websocket.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import psutil 5 | from typing import Callable 6 | from fastapi import WebSocket 7 | from starlette.websockets import WebSocketDisconnect 8 | 9 | # Logging 10 | from elysia.api.core.log import logger 11 | 12 | # Objects 13 | from elysia.api.utils.default_payloads import error_payload 14 | 15 | 16 | async def help_websocket(websocket: WebSocket, ws_route: Callable): 17 | memory_process = psutil.Process() 18 | # initial_memory = memory_process.memory_info().rss 19 | try: 20 | await websocket.accept() 21 | while True: 22 | try: 23 | # start_time = time.time() 24 | # Wait for a message from the client 25 | # logger.info(f"Memory usage before receiving: {psutil.Process().memory_info().rss / 1024 / 1024}MB") 26 | try: 27 | data = None 28 | last_communication = time.time() 29 | 30 | # Custom timeout handler 31 | while True: 32 | if time.time() - last_communication > 60: 33 | # logger.warning("No communication for 60 seconds - sending heartbeat") 34 | await websocket.send_json({"type": "heartbeat"}) 35 | last_communication = time.time() 36 | 37 | try: 38 | # Use a short timeout for receive_json to allow checking the timer 39 | data = await asyncio.wait_for( 40 | websocket.receive_json(), timeout=1.0 41 | ) 42 | last_communication = time.time() 43 | 44 | # Process the received data 45 | await ws_route(data, websocket) 46 | last_communication = ( 47 | time.time() 48 | ) # Update timer after processing 49 | 50 | # Check if it's a disconnect request 51 | if data.get("type") == "disconnect": 52 | return 53 | 54 | except asyncio.TimeoutError: 55 | continue 56 | 57 | except Exception as e: 58 | error = error_payload(text=str(e), conversation_id="", query_id="") 59 | await websocket.send_json(error) 60 | logger.error(f"Error in websocket communication: {str(e)}") 61 | 62 | # logger.info(f"Memory usage after receiving: {psutil.Process().memory_info().rss / 1024 / 1024}MB") 63 | # logger.info(f"Processing time: {time.time() - start_time}s") 64 | 65 | except WebSocketDisconnect: 66 | # logger.info("WebSocket disconnected", exc_info=True) 67 | break # Exit the loop on disconnect 68 | 69 | except RuntimeError as e: 70 | if ( 71 | "Cannot call 'receive' once a disconnect message has been received" 72 | in str(e) 73 | ): 74 | # logger.info("WebSocket already disconnected") 75 | break # Exit the loop if the connection is already closed 76 | else: 77 | raise # Re-raise other RuntimeErrors 78 | 79 | except Exception as e: 80 | logger.error(f"Error in WebSocket: {str(e)}") 81 | try: 82 | if data and "conversation_id" in data and "query_id" in data: 83 | error = error_payload( 84 | text=str(e), 85 | conversation_id=data["conversation_id"], 86 | query_id=data["query_id"], 87 | ) 88 | await websocket.send_json(error) 89 | elif data and "conversation_id" in data: 90 | error = error_payload( 91 | text=str(e), 92 | conversation_id=data["conversation_id"], 93 | query_id="", 94 | ) 95 | await websocket.send_json(error) 96 | else: 97 | error = error_payload( 98 | text=str(e), conversation_id="", query_id="" 99 | ) 100 | await websocket.send_json(error) 101 | 102 | except RuntimeError: 103 | logger.warning( 104 | "Failed to send error message, WebSocket might be closed" 105 | ) 106 | break # Exit the loop after sending the error message 107 | 108 | except Exception as e: 109 | logger.warning(f"Closing WebSocket: {str(e)}") 110 | finally: 111 | try: 112 | await websocket.close() 113 | except RuntimeError: 114 | logger.info("WebSocket already closed") 115 | -------------------------------------------------------------------------------- /elysia/preprocessing/__init__.py: -------------------------------------------------------------------------------- 1 | from .collection import ( 2 | preprocess, 3 | preprocess_async, 4 | view_preprocessed_collection, 5 | view_preprocessed_collection_async, 6 | edit_preprocessed_collection, 7 | edit_preprocessed_collection_async, 8 | delete_preprocessed_collection, 9 | delete_preprocessed_collection_async, 10 | preprocessed_collection_exists, 11 | preprocessed_collection_exists_async, 12 | ) 13 | -------------------------------------------------------------------------------- /elysia/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/tools/__init__.py -------------------------------------------------------------------------------- /elysia/tools/postprocessing/__init__.py: -------------------------------------------------------------------------------- 1 | from .summarise_items import SummariseItems 2 | -------------------------------------------------------------------------------- /elysia/tools/postprocessing/prompt_templates.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | 3 | 4 | class ObjectSummaryPrompt(dspy.Signature): 5 | """ 6 | Given a list of objects (a list of dictionaries, where each item in the dictionary is a field from the object), 7 | you must provide a list of strings, where each string is a summary of the object. 8 | 9 | These objects can be of any type, and you should summarise them in a way that is useful to the user. 10 | """ 11 | 12 | objects: list[dict] = dspy.InputField( 13 | desc="The objects to summarise.", format=list[dict] 14 | ) 15 | summaries: list[str] = dspy.OutputField( 16 | desc=""" 17 | The summaries of each individual object, in a list of strings. 18 | These summaries should be extremely concise, and not aiming to summarise all objects, but a brief summary/description of _each_ object. 19 | Specifically, what the object is, what it is about to give the user a concise idea of the individual objects. A few sentences at most. 20 | Your output should be a list of strings in Python format, e.g. `["summary_1", "summary_2", ...]`. 21 | Do not enclose with ```python or ```. Just the list only. 22 | """.strip(), 23 | format=list[str], 24 | ) 25 | -------------------------------------------------------------------------------- /elysia/tools/postprocessing/summarise_items.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | 3 | from logging import Logger 4 | 5 | from elysia.objects import Status, Tool 6 | from elysia.tools.postprocessing.prompt_templates import ObjectSummaryPrompt 7 | from elysia.tree.objects import TreeData 8 | from elysia.util.client import ClientManager 9 | 10 | 11 | class SummariseItems(Tool): 12 | def __init__(self, logger: Logger | None = None, **kwargs): 13 | super().__init__( 14 | name="query_postprocessing", 15 | description=""" 16 | If the user has requested itemised summaries for retrieved objects, 17 | this tool summarises each object on an individual basis. 18 | """, 19 | status="", 20 | end=False, 21 | ) 22 | 23 | async def __call__( 24 | self, 25 | tree_data: TreeData, 26 | inputs: dict, 27 | base_lm: dspy.LM, 28 | complex_lm: dspy.LM, 29 | client_manager: ClientManager | None = None, 30 | **kwargs, 31 | ): 32 | 33 | if "items_to_summarise" in tree_data.environment.hidden_environment: 34 | 35 | # get objects from hidden environment 36 | objects_list = tree_data.environment.hidden_environment[ 37 | "items_to_summarise" 38 | ] 39 | 40 | for obj in objects_list: 41 | object_summariser = dspy.ChainOfThought(ObjectSummaryPrompt) 42 | yield Status( 43 | f"Summarising {len(obj.objects)} objects from {obj.metadata['collection_name']}..." 44 | ) 45 | 46 | summariser = await object_summariser.aforward( 47 | objects=obj.to_json(), lm=base_lm 48 | ) 49 | 50 | obj.add_summaries(summariser.summaries) 51 | 52 | yield obj 53 | 54 | # remove items from hidden environment 55 | tree_data.environment.hidden_environment.pop("items_to_summarise") 56 | -------------------------------------------------------------------------------- /elysia/tools/retrieval/__init__.py: -------------------------------------------------------------------------------- 1 | from .query import Query 2 | from .aggregate import Aggregate 3 | from .objects import ( 4 | MessageRetrieval, 5 | ConversationRetrieval, 6 | DocumentRetrieval, 7 | Aggregation, 8 | ) 9 | -------------------------------------------------------------------------------- /elysia/tools/text/__init__.py: -------------------------------------------------------------------------------- 1 | from .text import Summarizer, CitedSummarizer, TextResponse, FakeTextResponse 2 | from .objects import TextWithCitation, TextWithTitle, TextWithCitations 3 | -------------------------------------------------------------------------------- /elysia/tools/text/objects.py: -------------------------------------------------------------------------------- 1 | from elysia.objects import Text 2 | from typing import List 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class TextWithCitation(BaseModel): 7 | text: str = Field(description="The text within the summary") 8 | ref_ids: List[str] = Field( 9 | description=( 10 | "The ref_ids of the citations relevant to the text. " 11 | "Can be an empty list if the text is not related to any of the citations." 12 | ), 13 | default_factory=list, 14 | ) 15 | 16 | 17 | class TextWithTitle(Text): 18 | def __init__(self, text: str, title: str, **kwargs): 19 | Text.__init__( 20 | self, 21 | "text_with_title", 22 | [{"text": text}], 23 | metadata={"title": title}, 24 | **kwargs, 25 | ) 26 | 27 | 28 | class TextWithCitations(Text): 29 | def __init__(self, cited_texts: List[TextWithCitation], title: str, **kwargs): 30 | Text.__init__( 31 | self, 32 | "text_with_citations", 33 | [ 34 | { 35 | "text": cited_text_item.text, 36 | "ref_ids": cited_text_item.ref_ids, 37 | } 38 | for cited_text_item in cited_texts 39 | ], 40 | metadata={"title": title}, 41 | **kwargs, 42 | ) 43 | -------------------------------------------------------------------------------- /elysia/tools/visualisation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/elysia/tools/visualisation/__init__.py -------------------------------------------------------------------------------- /elysia/tools/visualisation/objects.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from pydantic.fields import Field 3 | 4 | from typing import Optional, Literal, Any 5 | from datetime import datetime 6 | from elysia.objects import Result 7 | 8 | 9 | class BarChartData(BaseModel): 10 | x_labels: list[str | int | float] = Field( 11 | description="The shared labels for the x-axis. Must be all string or all numeric. Cannot mix.", 12 | min_length=1, 13 | max_length=10, 14 | ) 15 | y_values: dict[str, list[int | float]] = Field( 16 | description="A dictionary of data points for the y-axis, with the key being the category/group for the data points.", 17 | min_length=1, 18 | max_length=10, 19 | ) 20 | 21 | 22 | class BarChart(BaseModel): 23 | title: str 24 | description: str 25 | x_axis_label: str 26 | y_axis_label: str 27 | data: BarChartData = Field( 28 | description="A dictionary of bar chart data points, with the key being the category/group for the data points." 29 | ) 30 | 31 | 32 | class HistogramData(BaseModel): 33 | distribution: list[float | int] 34 | 35 | 36 | class HistogramChart(BaseModel): 37 | title: str 38 | description: str 39 | data: dict[str, HistogramData] = Field( 40 | description="A dictionary of histogram data points, with the key being the category/grouping for the data points." 41 | ) 42 | 43 | 44 | class ScatterOrLineDataPoint(BaseModel): 45 | value: int | float | datetime | str 46 | label: Optional[str] = Field( 47 | default="", 48 | description="If a data point is highlighted, you can label it.", 49 | min_length=1, 50 | max_length=50, 51 | ) 52 | 53 | 54 | class ScatterOrLineYAxisData(BaseModel): 55 | label: str = Field( 56 | description="The label for the data points going into the y-axis. " 57 | ) 58 | kind: Literal["scatter", "line"] = Field( 59 | description="Whether the data points (for this y-axis data) are scatter points, or a line plot. " 60 | ) 61 | data_points: list[ScatterOrLineDataPoint] 62 | 63 | 64 | class ScatterOrLineDataPoints(BaseModel): 65 | x_axis: list[ScatterOrLineDataPoint] = Field( 66 | default=[], 67 | description=( 68 | "The shared x-axis data points. This MUST be populated. " 69 | "If you think there is no relevant data for the x-axis, " 70 | "do not leave it empty. X-values are required for Y-values to be plotted. " 71 | ), 72 | ) 73 | y_axis: list[ScatterOrLineYAxisData] = Field( 74 | default=[], 75 | description=( 76 | "Each element of the list has a label for the data points going into the y-axis. " 77 | "Each one is either a set of scatter points, or a line plot. " 78 | "Which can be combined in a single chart, and will be automatically separated into groups in the same chart (with labels). " 79 | "There can be multiple sets of data, under different labels, indicating different groups of data. " 80 | "These will be plotted on the same y-axis, but with different colours, shapes, etc." 81 | ), 82 | min_length=1, 83 | max_length=20, 84 | ) 85 | normalize_y_axis: Optional[bool] = Field( 86 | default=False, 87 | description=( 88 | "If true, all values in the y-axis will be normalized to the range 0-1. " 89 | "Useful if the two values are not on the same scale. " 90 | "Do not modify the labels, or the values, these will be automatically changed." 91 | ), 92 | ) 93 | 94 | 95 | class ScatterOrLineChart(BaseModel): 96 | title: str 97 | description: str 98 | x_axis_label: str 99 | y_axis_label: str 100 | data: ScatterOrLineDataPoints = Field( 101 | description="A dictionary of scatter or line data points, with the key being the label for the data points." 102 | ) 103 | 104 | 105 | class ChartResult(Result): 106 | def __init__( 107 | self, 108 | charts: list[BarChart | HistogramChart | ScatterOrLineChart], 109 | chart_type: Literal["bar", "histogram", "scatter_or_line"], 110 | title: str = "", 111 | metadata: dict[str, Any] = {}, 112 | ): 113 | if len(charts) == 1 and title == "": 114 | title = charts[0].title 115 | 116 | super().__init__( 117 | objects=[chart.model_dump() for chart in charts], 118 | metadata={"chart_title": title, "chart_type": chart_type, **metadata}, 119 | payload_type=f"{chart_type}_chart", 120 | mapping=None, 121 | name=f"{chart_type}_chart", 122 | ) 123 | 124 | def llm_parse(self): 125 | if "impossible" in self.metadata: 126 | out = f"Model judged creation of {self.metadata['chart_type']} chart to be impossible for reason: " 127 | if "impossible_reasoning" in self.metadata: 128 | out += f"'{self.metadata['impossible_reasoning']}'. " 129 | out += "Returning to the decision tree..." 130 | return out 131 | else: 132 | return f"Created {len(self.objects)} {self.metadata['chart_type']} chart(s) titled '{self.metadata['chart_title']}'." 133 | -------------------------------------------------------------------------------- /elysia/tools/visualisation/prompt_templates.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | from elysia.tools.visualisation.objects import ( 3 | BarChart, 4 | HistogramChart, 5 | ScatterOrLineChart, 6 | ) 7 | 8 | 9 | class CreateBarChart(dspy.Signature): 10 | """ 11 | Create one or more bar charts. 12 | 13 | Create a maximum of 9 bar charts. 14 | Each bar chart should have a maximum of 10 categories. 15 | Hence each chart should also have a maximum of 10 values per category. 16 | Pick the most relevant categories and values. 17 | """ 18 | 19 | charts: list[BarChart] = dspy.OutputField(description="The bar chart to create.") 20 | overall_title: str = dspy.OutputField( 21 | description=( 22 | "If providing more than one chart, they will be displayed in a grid. " 23 | "This is the overall title for above the grid. " 24 | "Otherwise provide an empty string. " 25 | ), 26 | ) 27 | 28 | 29 | class CreateHistogramChart(dspy.Signature): 30 | """ 31 | Create one or more histogram charts. 32 | 33 | Create a maximum of 9 histogram charts. 34 | Do not produce more than 50 values per histogram chart. Pick the most relevant values. 35 | """ 36 | 37 | charts: list[HistogramChart] = dspy.OutputField( 38 | description="The histogram chart to create." 39 | ) 40 | overall_title: str = dspy.OutputField( 41 | description=( 42 | "If providing more than one chart, they will be displayed in a grid. " 43 | "This is the overall title for above the grid. " 44 | "Otherwise provide an empty string. " 45 | ), 46 | ) 47 | 48 | 49 | class CreateScatterOrLineChart(dspy.Signature): 50 | """ 51 | Create one or more scatter or line charts. 52 | 53 | Create a maximum of 9 scatter or line charts. 54 | Create a maximum of 50 points per scatter or line chart. Pick the most relevant points. 55 | 56 | A scatter or line chart can have multiple y-axis values, each with a different label. 57 | You can combine a line chart with a scatter chart by creating multiple 58 | """ 59 | 60 | charts: list[ScatterOrLineChart] = dspy.OutputField( 61 | description="The scatter or line chart to create." 62 | ) 63 | overall_title: str = dspy.OutputField( 64 | description=( 65 | "If providing more than one chart, they will be displayed in a grid. " 66 | "This is the overall title for above the grid. " 67 | "Otherwise provide an empty string. " 68 | ), 69 | ) 70 | 71 | 72 | # class CreateLineChart(dspy.Signature): 73 | # """ 74 | # Create one or more line charts. 75 | 76 | # Create a maximum of 9 line charts. 77 | # Create a maximum of 50 points per line chart. Pick the most relevant points. 78 | # """ 79 | 80 | # charts: list[ScatterOrLineChart] = dspy.OutputField( 81 | # description="The line chart to create." 82 | # ) 83 | # overall_title: str = dspy.OutputField( 84 | # description=( 85 | # "If providing more than one chart, they will be displayed in a grid. " 86 | # "This is the overall title for above the grid. " 87 | # "Otherwise provide an empty string. " 88 | # ), 89 | # ) 90 | -------------------------------------------------------------------------------- /elysia/tools/visualisation/visualise.py: -------------------------------------------------------------------------------- 1 | from logging import Logger 2 | from elysia.objects import Tool, Error 3 | from elysia.util.elysia_chain_of_thought import ElysiaChainOfThought 4 | from elysia.tools.visualisation.objects import ( 5 | ChartResult, 6 | BarChart, 7 | HistogramChart, 8 | ScatterOrLineChart, 9 | ) 10 | from elysia.tools.visualisation.util import convert_chart_types_to_matplotlib 11 | from elysia.tools.visualisation.prompt_templates import ( 12 | CreateBarChart, 13 | CreateHistogramChart, 14 | CreateScatterOrLineChart, 15 | ) 16 | from elysia.objects import Response 17 | from elysia.util.objects import TrainingUpdate, TreeUpdate 18 | 19 | 20 | class Visualise(Tool): 21 | 22 | def __init__(self, logger: Logger | None = None, **kwargs): 23 | super().__init__( 24 | name="visualise", 25 | description=( 26 | "Visualise data in a chart from the environment. " 27 | "You can only visualise data that is in the environment. " 28 | "If there is nothing relevant in the environment, do not choose this tool." 29 | ), 30 | status="Visualising...", 31 | end=True, 32 | inputs={ 33 | "chart_type": { 34 | "type": str, 35 | "description": ( 36 | "The type of chart to create. " 37 | "Must be one of: `bar`, `histogram`, `scatter_or_line`." 38 | ), 39 | "required": True, 40 | "default": "", 41 | } 42 | }, 43 | ) 44 | self.logger = logger 45 | 46 | async def __call__( 47 | self, 48 | tree_data, 49 | inputs: dict, 50 | base_lm, 51 | complex_lm, 52 | client_manager, 53 | **kwargs, 54 | ): 55 | chart_type = inputs["chart_type"] 56 | 57 | try: 58 | type_mapping = { 59 | "bar": CreateBarChart, 60 | "histogram": CreateHistogramChart, 61 | "scatter_or_line": CreateScatterOrLineChart, 62 | } 63 | 64 | create_chart = ElysiaChainOfThought( 65 | type_mapping[chart_type], 66 | tree_data=tree_data, 67 | environment=True, 68 | impossible=True, 69 | tasks_completed=True, 70 | reasoning=tree_data.settings.BASE_USE_REASONING, 71 | ) 72 | prediction = await create_chart.aforward(lm=base_lm) 73 | charts = prediction.charts 74 | title = prediction.overall_title 75 | 76 | yield Response(text=prediction.message_update) 77 | yield TrainingUpdate( 78 | module_name="visualise", 79 | inputs={ 80 | "chart_type": chart_type, 81 | **tree_data.to_json(), 82 | }, 83 | outputs=prediction.__dict__["_store"], 84 | ) 85 | 86 | if prediction.impossible: 87 | yield ChartResult( 88 | [], 89 | chart_type, 90 | title, 91 | metadata={ 92 | "impossible": True, 93 | "impossible_reasoning": ( 94 | prediction.reasoning 95 | if tree_data.settings.BASE_USE_REASONING 96 | else "" 97 | ), 98 | }, 99 | ) 100 | return 101 | 102 | if self.logger and self.logger.level <= 20: 103 | fig = convert_chart_types_to_matplotlib(charts) 104 | fig.show() 105 | 106 | yield ChartResult(charts, chart_type, title) 107 | except Exception as e: 108 | yield Error(f"Error visualising data: {str(e)}") 109 | 110 | async def is_tool_available(self, tree_data, base_lm, complex_lm, client_manager): 111 | """ 112 | Available when there are relevant objects in the environment. 113 | """ 114 | return not tree_data.environment.is_empty() 115 | -------------------------------------------------------------------------------- /elysia/tree/__init__.py: -------------------------------------------------------------------------------- 1 | from elysia.tree.objects import Environment, CollectionData, TreeData 2 | from elysia.tree.tree import Tree 3 | -------------------------------------------------------------------------------- /elysia/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import ClientManager 2 | from .objects import ( 3 | TreeUpdate, 4 | TrainingUpdate, 5 | FewShotExamples, 6 | ) 7 | -------------------------------------------------------------------------------- /elysia/util/async_util.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import nest_asyncio 3 | import inspect 4 | import types 5 | 6 | 7 | def _to_task(future, as_task, loop): 8 | if not as_task or isinstance(future, asyncio.Task): 9 | return future 10 | return loop.create_task(future) 11 | 12 | 13 | def asyncio_run(future, as_task=True): 14 | """ 15 | From: https://stackoverflow.com/questions/46827007/error-runtimeerror-this-event-loop-is-already-running-in-python 16 | 17 | A better implementation of `asyncio.run`. 18 | 19 | :param future: A future or task or call of an async method. 20 | :param as_task: Forces the future to be scheduled as task (needed for e.g. aiohttp). 21 | """ 22 | 23 | try: 24 | loop = asyncio.get_running_loop() 25 | except RuntimeError: # No event loop running: 26 | loop = asyncio.new_event_loop() 27 | return loop.run_until_complete(_to_task(future, as_task, loop)) 28 | else: 29 | nest_asyncio.apply(loop) 30 | return asyncio.run(_to_task(future, as_task, loop)) # type: ignore 31 | -------------------------------------------------------------------------------- /elysia/util/dummy_adapter.py: -------------------------------------------------------------------------------- 1 | from dspy import LM, configure, ChatAdapter 2 | from dspy.utils import DummyLM 3 | from contextlib import contextmanager 4 | from pydantic_core import PydanticUndefined 5 | 6 | 7 | class DummyAdapter(ChatAdapter): 8 | 9 | def __init__( 10 | self, callbacks: list | None = None, use_native_function_calling: bool = True 11 | ): 12 | super().__init__( 13 | callbacks=callbacks, use_native_function_calling=use_native_function_calling 14 | ) 15 | 16 | def __call__( 17 | self, 18 | lm: LM, 19 | lm_kwargs, 20 | signature, 21 | demos, 22 | inputs, 23 | ): 24 | 25 | categories = [] 26 | defaults = {} 27 | 28 | # set some specific defaults for different inputs 29 | 30 | # for decision node 31 | if "available_actions" in inputs: 32 | defaults["function_name"] = list(inputs["available_actions"].keys())[0] 33 | 34 | # for preprocessing return type assignment 35 | if "possible_return_types" in inputs: 36 | defaults["return_types"] = [list(inputs["possible_return_types"].keys())[0]] 37 | 38 | # for field mapping 39 | if "input_data_fields" in inputs and "output_data_fields" in inputs: 40 | defaults["field_mapping"] = { 41 | in_field: inputs["output_data_fields"][0] 42 | for in_field in inputs["input_data_fields"] 43 | } 44 | 45 | # otherwise, try to find the default value from the signature to ensure type checking 46 | for field_name, field in signature.model_fields.items(): 47 | if field.json_schema_extra.get("__dspy_field_type") == "output": 48 | 49 | categories.append(field_name) 50 | 51 | if field_name not in defaults: 52 | if field.default is not PydanticUndefined: 53 | defaults[field_name] = field.default 54 | else: 55 | try: 56 | defaults[field_name] = field.annotation() 57 | except Exception as e: 58 | try: 59 | defaults[field_name] = field.annotation() 60 | except Exception as e: 61 | try: 62 | defaults[field_name] = field.json_schema_extra[ 63 | "format" 64 | ]() 65 | except Exception as e: 66 | defaults[field_name] = "Test!" 67 | 68 | return super().__call__( 69 | DummyLM([{cat: defaults[cat] for cat in categories}]), 70 | lm_kwargs, 71 | signature, 72 | demos, 73 | inputs, 74 | ) 75 | 76 | async def acall( 77 | self, 78 | lm: LM, 79 | lm_kwargs, 80 | signature, 81 | demos, 82 | inputs, 83 | ): 84 | 85 | categories = [] 86 | defaults = {} 87 | 88 | # set some specific defaults for different inputs 89 | 90 | # for decision node 91 | if "available_actions" in inputs: 92 | defaults["function_name"] = list(inputs["available_actions"].keys())[0] 93 | 94 | # for preprocessing return type assignment 95 | if "possible_return_types" in inputs: 96 | defaults["return_types"] = [list(inputs["possible_return_types"].keys())[0]] 97 | 98 | # for field mapping 99 | if "input_data_fields" in inputs and "output_data_fields" in inputs: 100 | defaults["field_mapping"] = { 101 | in_field: inputs["output_data_fields"][0] 102 | for in_field in inputs["input_data_fields"] 103 | } 104 | 105 | # otherwise, try to find the default value from the signature to ensure type checking 106 | for field_name, field in signature.model_fields.items(): 107 | if field.json_schema_extra.get("__dspy_field_type") == "output": 108 | 109 | categories.append(field_name) 110 | 111 | if field_name not in defaults: 112 | if field.default is not PydanticUndefined: 113 | defaults[field_name] = field.default 114 | else: 115 | try: 116 | defaults[field_name] = field.annotation() 117 | except Exception as e: 118 | try: 119 | defaults[field_name] = field.annotation() 120 | except Exception as e: 121 | try: 122 | defaults[field_name] = field.json_schema_extra[ 123 | "format" 124 | ]() 125 | except Exception as e: 126 | defaults[field_name] = "Test!" 127 | 128 | return await super().acall( 129 | DummyLM([{cat: defaults[cat] for cat in categories}]), 130 | lm_kwargs, 131 | signature, 132 | demos, 133 | inputs, 134 | ) 135 | -------------------------------------------------------------------------------- /elysia/util/retrieve_feedback.py: -------------------------------------------------------------------------------- 1 | from elysia.util.client import ClientManager 2 | import json 3 | import dspy 4 | import random 5 | from weaviate.classes.query import Filter, MetadataQuery 6 | 7 | 8 | async def retrieve_feedback( 9 | client_manager: ClientManager, user_prompt: str, model: str, n: int = 6 10 | ): 11 | """ 12 | Retrieve similar examples from the database. 13 | """ 14 | 15 | # semantic search for similar examples 16 | async with client_manager.connect_to_async_client() as client: 17 | if not await client.collections.exists("ELYSIA_FEEDBACK__"): 18 | return [], [] 19 | 20 | feedback_collection = client.collections.get("ELYSIA_FEEDBACK__") 21 | 22 | # find superpositive examples 23 | superpositive_feedback = await feedback_collection.query.near_text( 24 | query=user_prompt, 25 | filters=Filter.all_of( 26 | [ 27 | Filter.by_property("modules_used").contains_any([model]), 28 | Filter.by_property("feedback").equal(2.0), 29 | ] 30 | ), 31 | certainty=0.7, 32 | limit=n, 33 | return_metadata=MetadataQuery(distance=True, certainty=True), 34 | ) 35 | 36 | if len(superpositive_feedback.objects) < n: 37 | 38 | # find positive examples 39 | positive_feedback = await feedback_collection.query.near_text( 40 | query=user_prompt, 41 | filters=Filter.all_of( 42 | [ 43 | Filter.by_property("modules_used").contains_any([model]), 44 | Filter.by_property("feedback").equal(1.0), 45 | ], 46 | ), 47 | certainty=0.7, 48 | limit=n, 49 | return_metadata=MetadataQuery(distance=True, certainty=True), 50 | ) 51 | 52 | feedback_objects = superpositive_feedback.objects 53 | feedback_objects.extend( 54 | positive_feedback.objects[: (n - len(superpositive_feedback.objects))] 55 | ) 56 | 57 | else: 58 | feedback_objects = superpositive_feedback.objects 59 | 60 | # get training updates 61 | training_updates = [ 62 | json.loads(f.properties["training_updates"]) # type: ignore 63 | for f in feedback_objects 64 | ] 65 | uuids = [str(f.uuid) for f in feedback_objects] 66 | 67 | relevant_updates = [] 68 | relevant_uuids = [] 69 | for i, update in enumerate(training_updates): 70 | for inner_update in update: 71 | if inner_update["module_name"] == model: 72 | relevant_updates.append(inner_update) 73 | relevant_uuids.append(uuids[i]) 74 | 75 | # take max n randomly selected updates 76 | random.shuffle(relevant_updates) 77 | relevant_updates = relevant_updates[:n] 78 | 79 | examples = [] 80 | for update in relevant_updates: 81 | examples.append( 82 | dspy.Example( 83 | { 84 | **{k: v for k, v in update["inputs"].items()}, 85 | **{k: v for k, v in update["outputs"].items()}, 86 | } 87 | ).with_inputs( 88 | *update["inputs"].keys(), 89 | ) 90 | ) 91 | 92 | return examples, relevant_uuids 93 | -------------------------------------------------------------------------------- /img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/img/architecture.png -------------------------------------------------------------------------------- /img/banner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/img/banner.gif -------------------------------------------------------------------------------- /img/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/img/config.png -------------------------------------------------------------------------------- /img/elysia.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/img/elysia.gif -------------------------------------------------------------------------------- /img/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/img/thumbnail.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Elysia API 2 | theme: 3 | name: material 4 | features: 5 | - navigation.tabs 6 | - navigation.sections 7 | - toc.integrate 8 | - navigation.top 9 | - search.suggest 10 | - search.highlight 11 | - content.tabs.link 12 | - content.code.annotation 13 | - content.code.copy 14 | language: en 15 | palette: 16 | - scheme: default 17 | toggle: 18 | icon: material/toggle-switch-off-outline 19 | name: Switch to dark mode 20 | primary: teal 21 | accent: purple 22 | - scheme: slate 23 | toggle: 24 | icon: material/toggle-switch 25 | name: Switch to light mode 26 | primary: teal 27 | accent: lime 28 | 29 | nav: 30 | - Getting Started: index.md 31 | - Setting Up: setting_up.md 32 | - Basic Usage: basic.md 33 | - Customising Elysia: advanced_usage.md 34 | - Creating a Tool: creating_tools.md 35 | - Advanced: 36 | - Overview: Advanced/index.md 37 | - Technical Overview: Advanced/technical_overview.md 38 | - Local Models: Advanced/local_models.md 39 | - Tool Construction: Advanced/advanced_tool_construction.md 40 | - Custom Objects: Advanced/custom_objects.md 41 | - Environment: Advanced/environment.md 42 | - API: 43 | - Overview: API/index.md 44 | - User Management: API/user_and_tree_managers.md 45 | - Payload Formats: API/payload_formats.md 46 | 47 | - Examples: 48 | - Index: Examples/index.md 49 | - Querying Weaviate: Examples/query_weaviate.md 50 | # - Sending an Automated Email: Examples/email.md 51 | - Basic Linear Regression: Examples/data_analysis.md 52 | 53 | - Reference: 54 | - Preprocessor: Reference/Preprocessor.md 55 | - Tree: Reference/Tree.md 56 | - WeaviateClient: Reference/Client.md 57 | - Managers: Reference/Managers.md 58 | - Settings: Reference/Settings.md 59 | - Objects: Reference/Objects.md 60 | # - Tools: Reference/Tools.md 61 | # - Result: Reference/Result.md 62 | - Payload Types: Reference/PayloadTypes.md 63 | - Util: Reference/Util.md 64 | plugins: 65 | - social 66 | - search 67 | - mkdocstrings 68 | 69 | extra: 70 | social: 71 | - icon: fontawesome/brands/github 72 | link: https://github.com/weaviate/elysia 73 | - icon: fontawesome/brands/github 74 | link: https://github.com/weaviate/elysia-frontend 75 | - icon: fontawesome/brands/github-alt 76 | link: https://github.com/dannyjameswilliams 77 | - icon: fontawesome/brands/github-alt 78 | link: https://github.com/thomashacker 79 | - icon: fontawesome/brands/linkedin 80 | link: https://www.linkedin.com/in/dannyjameswilliams/ 81 | - icon: fontawesome/brands/linkedin 82 | link: https://www.linkedin.com/in/edwardschmuhl/ 83 | 84 | markdown_extensions: 85 | - pymdownx.highlight: 86 | anchor_linenums: true 87 | - pymdownx.inlinehilite 88 | - pymdownx.snippets 89 | - admonition 90 | - pymdownx.arithmatex: 91 | generic: true 92 | - footnotes 93 | - pymdownx.details 94 | - pymdownx.superfences 95 | - pymdownx.mark 96 | - attr_list 97 | - pymdownx.emoji: 98 | emoji_index: !!python/name:material.extensions.emoji.twemoji 99 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 100 | - pymdownx.arithmatex: 101 | generic: true 102 | 103 | extra_javascript: 104 | - javascripts/mathjax.js 105 | - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling >= 1.26"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "elysia-ai" 7 | version = "0.3.dev1" 8 | description = "Elysia is an open-source agentic platform for searching data. It is built with customisation in mind, allowing you to build agents and tools that are tailored to your specific use case. It uses Weaviate as the default retrieval tools, and can interface with your data stored in a Weaviate cluster." 9 | readme = "README.md" 10 | authors = [ 11 | {name = "Danny", email = "danny@weaviate.io"} 12 | ] 13 | requires-python = ">=3.10.0,<3.13.0" 14 | classifiers = [ 15 | "Development Status :: 4 - Beta" 16 | ] 17 | keywords=["ai", "agentic", "weaviate", "retrieval", "search", "llm"] 18 | dependencies = [ 19 | "apscheduler==3.11.0", 20 | "bcrypt>=4.3.0", 21 | "dspy-ai>=3.0.0", 22 | "fastapi[standard]>=0.115.11", 23 | "httpx==0.28.1", 24 | "pympler==1.1", 25 | "python-multipart==0.0.18", 26 | "rich>=13.7.1,<=14.0.0", 27 | "blis>=1.2.0,<1.3.0", 28 | "spacy==3.8.7", 29 | "uvicorn[standard]==0.35.0", 30 | "weaviate-client>=4.16.7", 31 | "nest_asyncio==1.6.0", 32 | "psutil==7.0.0", 33 | "cryptography>=44.0.3", 34 | "matplotlib>=3.10.3", 35 | "litellm>=1.74.15,<1.76.0", 36 | "thinc>=8.3.4,<8.4.0", 37 | "pip>=25.2", 38 | ] 39 | packages=[{include="elysia"}] 40 | 41 | [project.urls] 42 | "Homepage" = "https://elysia.weaviate.io" 43 | "Documentation" = "https://weaviate.github.io/elysia/" 44 | 45 | [project.optional-dependencies] 46 | dev = [ 47 | "accelerate==1.5.2", 48 | "deepeval>=3.2.6", 49 | "pytest>=7.4.4", 50 | "pytest-asyncio>=0.21.2", 51 | "setuptools==78.1.0", 52 | "mkdocs-material[imaging]==9.6.12", 53 | "mkdocstrings[python]==0.29.1", 54 | "pillow==10.4.0", 55 | "cairosvg==2.7.1", 56 | "websocket-client==1.8.0", 57 | "pytest-cov>=6.2.1" 58 | ] 59 | 60 | [project.scripts] 61 | elysia = "elysia.api.cli:cli" 62 | 63 | [tool.hatch.build.targets.wheel] 64 | packages = ["elysia"] 65 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaviate/elysia/25e08ea31d7d222d7db3ede40ecf193bcdb89a33/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from elysia.api.dependencies.common import get_user_manager 3 | 4 | 5 | @pytest.fixture(scope="session", autouse=True) 6 | async def cleanup_clients(request): 7 | yield 8 | user_manager = get_user_manager() 9 | await user_manager.close_all_clients() 10 | -------------------------------------------------------------------------------- /tests/no_reqs/api/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | from elysia.util.dummy_adapter import DummyAdapter 4 | from dspy import configure, ChatAdapter, LM 5 | import dspy 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def event_loop(): 10 | try: 11 | loop = asyncio.get_running_loop() 12 | except RuntimeError: 13 | loop = asyncio.new_event_loop() 14 | try: 15 | yield loop 16 | finally: 17 | loop.close() 18 | 19 | 20 | @pytest.fixture(autouse=True, scope="module") 21 | def use_dummy_adapter(): 22 | dummy_adapter = DummyAdapter() 23 | configure(adapter=dummy_adapter) 24 | 25 | yield 26 | 27 | configure(adapter=ChatAdapter()) 28 | -------------------------------------------------------------------------------- /tests/no_reqs/api/test_api_keys_nr.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | 4 | from elysia.config import Settings 5 | from elysia.api.routes.init import initialise_user 6 | from elysia.api.routes.user_config import ( 7 | load_a_config, 8 | save_config_user, 9 | load_config_user, 10 | new_user_config, 11 | get_current_user_config, 12 | ) 13 | from elysia.api.api_types import SaveConfigUserData 14 | from fastapi.responses import JSONResponse 15 | import json 16 | from elysia.api.dependencies.common import get_user_manager 17 | from elysia.api.services.user import UserManager 18 | from weaviate.util import generate_uuid5 19 | 20 | from uuid import uuid4 21 | 22 | 23 | def read_response(response: JSONResponse): 24 | return json.loads(response.body) 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_set_api_keys(): 29 | 30 | user_id = f"test_{uuid4()}" 31 | user_manager = get_user_manager() 32 | 33 | openrouter_api_key = "some-random-key" 34 | 35 | # initialise the user 36 | response = await initialise_user(user_id=user_id, user_manager=user_manager) 37 | response = read_response(response) 38 | 39 | # create a new config 40 | response = await new_user_config(user_id=user_id, user_manager=user_manager) 41 | response = read_response(response) 42 | config_id = response["config"]["id"] 43 | 44 | new_config = response["config"] 45 | new_config["settings"]["API_KEYS"]["openrouter_api_key"] = openrouter_api_key 46 | 47 | response = await save_config_user( 48 | user_id=user_id, 49 | config_id=config_id, 50 | data=SaveConfigUserData( 51 | name=response["config"]["name"], 52 | default=True, 53 | config=new_config, 54 | frontend_config={ 55 | "save_configs_to_weaviate": False, 56 | }, 57 | ), 58 | user_manager=user_manager, 59 | ) 60 | response = read_response(response) 61 | assert response["error"] == "", response["error"] 62 | assert ( 63 | response["config"]["settings"]["API_KEYS"]["openrouter_api_key"] 64 | == openrouter_api_key 65 | ) 66 | 67 | # load a config 68 | response = await get_current_user_config(user_id=user_id, user_manager=user_manager) 69 | response = read_response(response) 70 | assert response["error"] == "", response["error"] 71 | assert ( 72 | response["config"]["settings"]["API_KEYS"]["openrouter_api_key"] 73 | == openrouter_api_key 74 | ) 75 | 76 | 77 | @pytest.mark.asyncio 78 | async def test_change_models(): 79 | 80 | user_id = "test_change_models" 81 | user_manager = get_user_manager() 82 | 83 | # initialise the user 84 | response = await initialise_user(user_id=user_id, user_manager=user_manager) 85 | response = read_response(response) 86 | 87 | # create a new config 88 | response = await new_user_config(user_id=user_id, user_manager=user_manager) 89 | response = read_response(response) 90 | config_id = response["config"]["id"] 91 | 92 | # change config, change the models 93 | new_config = response["config"] 94 | new_config["settings"]["BASE_MODEL"] = "gpt-4o" 95 | new_config["settings"]["COMPLEX_MODEL"] = "gpt-4o" 96 | new_config["settings"]["BASE_PROVIDER"] = "openrouter/openai" 97 | new_config["settings"]["COMPLEX_PROVIDER"] = "openrouter/openai" 98 | 99 | response = await save_config_user( 100 | user_id=user_id, 101 | config_id=config_id, 102 | data=SaveConfigUserData( 103 | name=response["config"]["name"], 104 | default=True, 105 | config=new_config, 106 | frontend_config={ 107 | "save_configs_to_weaviate": False, 108 | }, 109 | ), 110 | user_manager=user_manager, 111 | ) 112 | response = read_response(response) 113 | assert response["error"] == "", response["error"] 114 | assert response["config"]["settings"]["BASE_MODEL"] == "gpt-4o" 115 | assert response["config"]["settings"]["COMPLEX_MODEL"] == "gpt-4o" 116 | assert response["config"]["settings"]["BASE_PROVIDER"] == "openrouter/openai" 117 | assert response["config"]["settings"]["COMPLEX_PROVIDER"] == "openrouter/openai" 118 | 119 | # load a config 120 | response = await get_current_user_config(user_id=user_id, user_manager=user_manager) 121 | response = read_response(response) 122 | assert response["error"] == "", response["error"] 123 | assert response["config"]["settings"]["BASE_MODEL"] == "gpt-4o" 124 | assert response["config"]["settings"]["COMPLEX_MODEL"] == "gpt-4o" 125 | assert response["config"]["settings"]["BASE_PROVIDER"] == "openrouter/openai" 126 | assert response["config"]["settings"]["COMPLEX_PROVIDER"] == "openrouter/openai" 127 | -------------------------------------------------------------------------------- /tests/no_reqs/api/test_init_nr.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from fastapi.responses import JSONResponse 3 | import os 4 | import json 5 | 6 | from elysia.api.core.log import set_log_level 7 | from elysia.api.dependencies.common import get_user_manager 8 | 9 | set_log_level("CRITICAL") 10 | 11 | from elysia.api.routes.init import initialise_tree, initialise_user 12 | from elysia.api.api_types import InitialiseTreeData 13 | 14 | 15 | def read_response(response: JSONResponse): 16 | return json.loads(response.body) 17 | 18 | 19 | class TestTree: 20 | 21 | @pytest.mark.asyncio 22 | async def test_initialise_user(self): 23 | user_manager = get_user_manager() 24 | user_id = "test_new_user" 25 | 26 | # Initialise when a user does not exist 27 | out = await initialise_user( 28 | user_id, 29 | user_manager, 30 | ) 31 | 32 | response = read_response(out) 33 | assert response["error"] == "" 34 | assert response["user_exists"] is False 35 | 36 | # check config options are environment variables 37 | if "BASE_MODEL" in os.environ: 38 | assert response["config"]["settings"]["BASE_MODEL"] == os.getenv( 39 | "BASE_MODEL" 40 | ) 41 | if "OPENAI_API_KEY" in os.environ: 42 | assert response["config"]["settings"]["API_KEYS"][ 43 | "openai_api_key" 44 | ] == os.getenv("OPENAI_API_KEY") 45 | 46 | # Initialise when a user exists 47 | out = await initialise_user( 48 | user_id, 49 | user_manager, 50 | ) 51 | 52 | response = read_response(out) 53 | assert response["error"] == "" 54 | assert response["user_exists"] is True 55 | 56 | @pytest.mark.asyncio 57 | async def test_tree(self): 58 | 59 | user_manager = get_user_manager() 60 | user_id = "test_user" 61 | conversation_id = "test_conversation" 62 | 63 | out = await initialise_user( 64 | user_id, 65 | user_manager, 66 | ) 67 | response = read_response(out) 68 | assert response["error"] == "" 69 | 70 | out = await initialise_tree( 71 | user_id, 72 | conversation_id, 73 | InitialiseTreeData( 74 | low_memory=True, 75 | ), 76 | user_manager, 77 | ) 78 | 79 | response = read_response(out) 80 | assert response["error"] == "" 81 | 82 | out = await initialise_tree( 83 | user_id + "2", 84 | conversation_id + "2", 85 | InitialiseTreeData( 86 | low_memory=True, 87 | ), 88 | user_manager, 89 | ) 90 | 91 | response = read_response(out) 92 | assert response["error"] != "" # should error as user doesn't exist 93 | -------------------------------------------------------------------------------- /tests/no_reqs/api/test_managers_nr.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import dotenv 4 | 5 | from elysia.api.services.user import UserManager 6 | from elysia.api.services.tree import TreeManager 7 | from elysia.config import Settings 8 | from elysia.api.utils.config import Config 9 | 10 | dotenv.load_dotenv(override=True) 11 | 12 | from uuid import uuid4 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_user_manager(): 17 | """ 18 | Test basic user manager functionality. 19 | """ 20 | user_id = f"test_{uuid4()}" 21 | conversation_id = f"test_{uuid4()}" 22 | 23 | user_manager = UserManager() 24 | 25 | # will raise error if no user created 26 | with pytest.raises(ValueError): 27 | await user_manager.get_user_local(user_id) 28 | 29 | # add a user 30 | await user_manager.add_user_local(user_id) 31 | 32 | # get user 33 | user = await user_manager.get_user_local(user_id) 34 | assert user is not None 35 | 36 | # will raise error if no tree created 37 | with pytest.raises(ValueError): 38 | await user_manager.get_tree(user_id, conversation_id) 39 | 40 | # add a tree 41 | await user_manager.initialise_tree(user_id, conversation_id, low_memory=True) 42 | 43 | # get tree 44 | tree = await user_manager.get_tree(user_id, conversation_id) 45 | assert tree is not None 46 | 47 | 48 | @pytest.mark.asyncio 49 | async def test_tree_manager(): 50 | """ 51 | Test basic tree manager functionality. 52 | """ 53 | user_id = f"test_{uuid4()}" 54 | conversation_id = f"test_{uuid4()}" 55 | 56 | tree_manager = TreeManager(user_id) 57 | 58 | # will raise error if no tree created 59 | with pytest.raises(ValueError): 60 | tree_manager.get_tree(conversation_id) 61 | 62 | # add a tree 63 | tree_manager.add_tree(conversation_id, low_memory=True) 64 | 65 | # get tree 66 | tree = tree_manager.get_tree(conversation_id) 67 | assert tree is not None 68 | -------------------------------------------------------------------------------- /tests/no_reqs/api/test_query_nr.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | from fastapi.responses import JSONResponse 4 | 5 | import json 6 | 7 | from elysia.api.core.log import logger, set_log_level 8 | from elysia.api.dependencies.common import get_user_manager 9 | 10 | set_log_level("CRITICAL") 11 | 12 | from elysia.api.routes.query import process 13 | from elysia.api.api_types import QueryData, InitialiseTreeData, SaveConfigUserData 14 | from elysia.api.routes.init import initialise_user, initialise_tree 15 | from elysia.api.routes.user_config import save_config_user 16 | 17 | from uuid import uuid4 18 | 19 | 20 | def read_response(response: JSONResponse): 21 | return json.loads(response.body) 22 | 23 | 24 | class fake_websocket: 25 | results = [] 26 | 27 | async def send_json(self, data: dict): 28 | self.results.append(data) 29 | 30 | 31 | async def initialise_user_and_tree(user_id: str, conversation_id: str): 32 | user_manager = get_user_manager() 33 | 34 | response = await initialise_user( 35 | user_id, 36 | user_manager, 37 | ) 38 | 39 | response = await initialise_tree( 40 | user_id, 41 | conversation_id, 42 | InitialiseTreeData( 43 | low_memory=True, 44 | ), 45 | user_manager, 46 | ) 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_query(): 51 | """ 52 | Test the query endpoint. 53 | """ 54 | 55 | user_id = f"test_{uuid4()}" 56 | conversation_id = f"test_{uuid4()}" 57 | query_id = f"test_{uuid4()}" 58 | config_id = f"test_{uuid4()}" 59 | 60 | websocket = fake_websocket() 61 | 62 | await initialise_user_and_tree(user_id, conversation_id) 63 | 64 | # set config for models 65 | response = await save_config_user( 66 | user_id=user_id, 67 | config_id=config_id, 68 | data=SaveConfigUserData( 69 | name="test_config", 70 | default=True, 71 | config={ 72 | "settings": { 73 | "BASE_MODEL": "gpt-4o-mini", 74 | "BASE_PROVIDER": "openai", 75 | "COMPLEX_MODEL": "gpt-4o", 76 | "COMPLEX_PROVIDER": "openai", 77 | }, 78 | }, 79 | frontend_config={ 80 | "save_trees_to_weaviate": False, 81 | "save_configs_to_weaviate": False, 82 | }, 83 | ), 84 | user_manager=get_user_manager(), 85 | ) 86 | response = read_response(response) 87 | assert response["error"] == "", response["error"] 88 | 89 | out = await process( 90 | QueryData( 91 | user_id=user_id, 92 | conversation_id=conversation_id, 93 | query="hi!", 94 | query_id=query_id, 95 | collection_names=[ 96 | "Test_ELYSIA_collection_1", 97 | "Test_ELYSIA_collection_2", 98 | ], 99 | ).model_dump(), 100 | websocket, 101 | get_user_manager(), 102 | ) 103 | 104 | # check all payloads are valid 105 | ner_found = False 106 | title_found = False 107 | complete_found = False 108 | for i, result in enumerate(websocket.results): 109 | if result["type"] == "ner": 110 | ner_found = True 111 | if result["type"] == "title": 112 | title_found = True 113 | if result["type"] == "completed": 114 | complete_found = True 115 | 116 | assert result["type"] != "error", result["payload"]["text"] 117 | assert isinstance(result, dict) 118 | assert "type" in result 119 | assert "id" in result 120 | assert "conversation_id" in result 121 | assert "query_id" in result 122 | assert "payload" in result 123 | assert isinstance(result["payload"], dict) 124 | if "objects" in result["payload"]: 125 | for obj in result["payload"]["objects"]: 126 | assert isinstance(obj, dict) 127 | if "metadata" in result["payload"]: 128 | assert isinstance(result["payload"]["metadata"], dict) 129 | if "text" in result["payload"]: 130 | assert isinstance(result["payload"]["text"], str) 131 | 132 | if result["type"] == "ner": 133 | assert isinstance(result["payload"]["text"], str) 134 | assert isinstance(result["payload"]["entity_spans"], list) 135 | assert isinstance(result["payload"]["noun_spans"], list) 136 | assert isinstance(result["payload"]["error"], str) 137 | assert result["payload"]["error"] == "" 138 | 139 | if result["type"] == "title": 140 | assert isinstance(result["payload"]["title"], str) 141 | assert isinstance(result["payload"]["error"], str) 142 | assert result["payload"]["error"] == "" 143 | 144 | if result["type"] == "completed": 145 | assert i == len(websocket.results) - 1 146 | assert websocket.results[i - 1]["type"] == "title" 147 | 148 | assert ner_found 149 | assert title_found 150 | assert complete_found 151 | -------------------------------------------------------------------------------- /tests/no_reqs/general/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | from elysia.util.dummy_adapter import DummyAdapter 4 | from dspy import configure, ChatAdapter, LM 5 | import dspy 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def event_loop(): 10 | try: 11 | loop = asyncio.get_running_loop() 12 | except RuntimeError: 13 | loop = asyncio.new_event_loop() 14 | try: 15 | yield loop 16 | finally: 17 | loop.close() 18 | 19 | 20 | # Global variable to track adapter state 21 | _current_adapter_type = None 22 | 23 | 24 | def get_current_adapter_type(): 25 | """Helper function to check what type of adapter is currently configured""" 26 | global _current_adapter_type 27 | return _current_adapter_type 28 | 29 | 30 | @pytest.fixture(autouse=True, scope="module") 31 | def use_dummy_adapter(): 32 | global _current_adapter_type 33 | 34 | dummy_adapter = DummyAdapter() 35 | configure(adapter=dummy_adapter) 36 | _current_adapter_type = "dummy" 37 | 38 | yield 39 | 40 | configure(adapter=ChatAdapter()) 41 | _current_adapter_type = "chat" 42 | 43 | 44 | if __name__ == "__main__": 45 | from dspy.adapters import JSONAdapter 46 | 47 | configure(adapter=DummyAdapter()) 48 | print(get_current_adapter_type()) 49 | 50 | from elysia.tree.util import DecisionNode 51 | from elysia.tree.tree import Tree 52 | from elysia.util.client import ClientManager 53 | 54 | lm = dspy.LM("gpt-4o-mini") 55 | 56 | # class TestSignature(dspy.Signature): 57 | # input = dspy.InputField(description="input") 58 | # output = dspy.OutputField(description="output") 59 | 60 | # class TestModule(dspy.Module): 61 | # def __init__(self): 62 | # self.mod = dspy.ChainOfThought(TestSignature) 63 | 64 | # def forward(self, input: str, lm: dspy.LM): 65 | # return self.mod(input=input, lm=lm) 66 | 67 | # def aforward(self, input: str, lm: dspy.LM): 68 | # return self.mod.aforward(input=input, lm=lm) 69 | 70 | # class NonModuleClass: 71 | # def __init__(self): 72 | # self.mod = TestModule() 73 | 74 | # def __call__(self, input: str, lm: dspy.LM): 75 | # return self.mod(input=input, lm=lm) 76 | 77 | # async def aforward(self, input: str, lm: dspy.LM): 78 | # return await self.mod.aforward(input=input, lm=lm) 79 | 80 | # async def test_non_module_class(): 81 | # non_module_class = NonModuleClass() 82 | # return await non_module_class.aforward(input="hi", lm=lm) 83 | 84 | # print(asyncio.run(test_non_module_class())) 85 | 86 | tree = Tree() 87 | 88 | decision_node = DecisionNode( 89 | id="test_decision_node", 90 | instruction="test_instruction", 91 | options={ 92 | "text_response": { 93 | "description": "test_description", 94 | "inputs": { 95 | "text": { 96 | "description": "response", 97 | "type": str, 98 | "default": "test_default", 99 | } 100 | }, 101 | "end": True, 102 | "status": "running", 103 | } 104 | }, 105 | ) 106 | 107 | async def test_decision_node(): 108 | 109 | out = await decision_node( 110 | tree_data=tree.tree_data, 111 | base_lm=lm, 112 | complex_lm=lm, 113 | available_tools=["text_response"], 114 | unavailable_tools=[], 115 | successive_actions={}, 116 | client_manager=ClientManager(), 117 | ) 118 | 119 | return out[0].reasoning 120 | 121 | print(asyncio.run(test_decision_node())) 122 | -------------------------------------------------------------------------------- /tests/no_reqs/general/test_chunker_nr.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import inspect 3 | 4 | from elysia.tools.retrieval.chunk import AsyncCollectionChunker, Chunker 5 | from elysia.util.client import ClientManager 6 | 7 | from weaviate.classes.query import QueryReference 8 | from weaviate.classes.config import Configure, DataType, Property, ReferenceProperty 9 | from weaviate.collections.classes.data import DataObject, DataReference 10 | from weaviate.collections import CollectionAsync 11 | from weaviate.collections.classes.internal import Object, QueryReturn 12 | from weaviate.collections.classes.config_vectorizers import _Vectorizer 13 | from weaviate.exceptions import WeaviateInvalidInputError 14 | from weaviate.util import generate_uuid5 15 | from weaviate.client import WeaviateAsyncClient 16 | 17 | 18 | def test_chunker(): 19 | chunker = Chunker(chunking_strategy="sentences", num_sentences=1) 20 | doc = "Hello, world! This is a test." 21 | chunks, spans = chunker.chunk(doc) 22 | assert len(chunks) == 2 23 | assert spans == [(0, 13), (14, 29)] 24 | assert chunks[0] == "Hello, world!" 25 | assert chunks[1] == "This is a test." 26 | assert doc[spans[0][0] : spans[0][1]] == chunks[0] 27 | assert doc[spans[1][0] : spans[1][1]] == chunks[1] 28 | 29 | doc = "Hello, world! This is a test. This is another test." 30 | chunks, spans = chunker.chunk(doc) 31 | assert len(chunks) == 3 32 | assert spans == [(0, 13), (14, 29), (30, 51)] 33 | assert chunks[0] == "Hello, world!" 34 | assert chunks[1] == "This is a test." 35 | assert chunks[2] == "This is another test." 36 | assert doc[spans[0][0] : spans[0][1]] == chunks[0] 37 | assert doc[spans[1][0] : spans[1][1]] == chunks[1] 38 | assert doc[spans[2][0] : spans[2][1]] == chunks[2] 39 | -------------------------------------------------------------------------------- /tests/no_reqs/general/test_save_load_nr.py: -------------------------------------------------------------------------------- 1 | from elysia.tree.tree import Tree 2 | from elysia.tree.util import get_saved_trees_weaviate 3 | 4 | from elysia.util.client import ClientManager 5 | 6 | import pytest 7 | 8 | 9 | def test_save_load_local(): 10 | 11 | tree = Tree( 12 | user_id="test_save_load_user", 13 | conversation_id="test_save_load_conversation", 14 | low_memory=True, 15 | style="This is a test style!", 16 | agent_description="This is a test agent description!", 17 | end_goal="This is a test end goal!", 18 | ) 19 | tree.tree_data.environment.hidden_environment["Example Entry"] = ( 20 | "This is an example!" 21 | ) 22 | 23 | # save the tree to a file 24 | tree_json = tree.export_to_json() 25 | 26 | # load the tree from the file 27 | loaded_tree = Tree.import_from_json(tree_json) 28 | 29 | assert tree.user_id == loaded_tree.user_id 30 | assert tree.conversation_id == loaded_tree.conversation_id 31 | assert tree.low_memory == loaded_tree.low_memory 32 | assert tree.tree_data.atlas.style == loaded_tree.tree_data.atlas.style 33 | assert ( 34 | tree.tree_data.atlas.agent_description 35 | == loaded_tree.tree_data.atlas.agent_description 36 | ) 37 | assert tree.tree_data.atlas.end_goal == loaded_tree.tree_data.atlas.end_goal 38 | assert "Example Entry" in loaded_tree.tree_data.environment.hidden_environment 39 | assert ( 40 | tree.tree_data.environment.hidden_environment["Example Entry"] 41 | == loaded_tree.tree_data.environment.hidden_environment["Example Entry"] 42 | ) 43 | -------------------------------------------------------------------------------- /tests/no_reqs/general/test_tree_nr.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from elysia.config import Settings 3 | from elysia.tree.tree import Tree 4 | from elysia.util.client import ClientManager 5 | 6 | 7 | def test_tree_init(): 8 | tree = Tree() 9 | assert tree is not None 10 | 11 | 12 | def test_tree_init_with_settings(): 13 | settings = Settings.from_smart_setup() 14 | tree = Tree(settings=settings) 15 | assert tree is not None 16 | 17 | 18 | def test_run_tree(): 19 | settings = Settings() 20 | settings.configure( 21 | base_model="gpt-4o-mini", 22 | base_provider="openai", 23 | complex_model="gpt-4o", 24 | complex_provider="openai", 25 | ) 26 | tree = Tree(settings=settings) 27 | response, objects = tree.run("Hi!") 28 | assert response is not None and len(response) > 0 29 | assert len(objects) is not None 30 | 31 | 32 | @pytest.mark.asyncio 33 | async def test_init_tree_with_no_client(): 34 | settings = Settings() 35 | settings.configure( 36 | base_model="gpt-4o-mini", 37 | base_provider="openai", 38 | complex_model="gpt-4o", 39 | complex_provider="openai", 40 | ) 41 | settings.WCD_URL = "" 42 | settings.WCD_API_KEY = "" 43 | 44 | tree = Tree(settings=settings) 45 | 46 | assert not ( 47 | await tree.tools["query"].is_tool_available( 48 | tree_data=tree.tree_data, 49 | base_lm=tree.base_lm, 50 | complex_lm=tree.complex_lm, 51 | client_manager=ClientManager( 52 | wcd_url=settings.WCD_URL, wcd_api_key=settings.WCD_API_KEY 53 | ), 54 | ) 55 | ) 56 | assert not ( 57 | await tree.tools["aggregate"].is_tool_available( 58 | tree_data=tree.tree_data, 59 | base_lm=tree.base_lm, 60 | complex_lm=tree.complex_lm, 61 | client_manager=ClientManager( 62 | wcd_url=settings.WCD_URL, wcd_api_key=settings.WCD_API_KEY 63 | ), 64 | ) 65 | ) 66 | -------------------------------------------------------------------------------- /tests/requires_env/README.md: -------------------------------------------------------------------------------- 1 | # Tests that require environment variables 2 | 3 | To run these tests, you need the following: 4 | 5 | ```bash 6 | WCD_URL=... 7 | WCD_API_KEY=... 8 | ``` 9 | and 10 | ```bash 11 | OPENROUTER_API_KEY=... 12 | OPENAI_API_KEY=... 13 | ``` 14 | in your local `.env` file. 15 | 16 | These tests deal with various items, such as checking API keys are correctly configured on running decision trees and similar functions. 17 | They also deal with _running LLMs_, in a non-trivial amount. **Running these tests WILL cost you credits**. 18 | 19 | You are not required to be able to run these tests to contribute to Elysia. Instead, ensure that the tests in `no_reqs/` pass instead. -------------------------------------------------------------------------------- /tests/requires_env/api/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | from elysia.util.dummy_adapter import DummyAdapter 4 | from dspy import configure, ChatAdapter, LM 5 | import dspy 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def event_loop(): 10 | try: 11 | loop = asyncio.get_running_loop() 12 | except RuntimeError: 13 | loop = asyncio.new_event_loop() 14 | try: 15 | yield loop 16 | finally: 17 | loop.close() 18 | -------------------------------------------------------------------------------- /tests/requires_env/api/test_managers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import dotenv 4 | 5 | from elysia.api.services.user import UserManager 6 | from elysia.api.services.tree import TreeManager 7 | from elysia.config import Settings 8 | from elysia.api.utils.config import Config 9 | 10 | dotenv.load_dotenv(override=True) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_config_in_managers(): 15 | """ 16 | Test updating settings and API keys in the user manager and tree manager. 17 | """ 18 | user_id = "test_user_config" 19 | conversation_id = "test_conversation_config" 20 | 21 | user_manager = UserManager() 22 | 23 | # add a tree manager to the user manager without a settings 24 | await user_manager.add_user_local(user_id) 25 | 26 | # add a tree to the user manager 27 | await user_manager.initialise_tree(user_id, conversation_id, low_memory=True) 28 | 29 | # get tree manager 30 | tree_manager = user_manager.users[user_id]["tree_manager"] 31 | 32 | # check that settings is equal to environment variables 33 | assert tree_manager.settings.API_KEYS["openai_api_key"] == os.environ.get( 34 | "OPENAI_API_KEY" 35 | ) 36 | assert tree_manager.settings.WCD_URL == os.environ.get("WCD_URL") 37 | assert tree_manager.settings.WCD_API_KEY == os.environ.get("WCD_API_KEY") 38 | 39 | # check that the tree has these settings too 40 | tree = await user_manager.get_tree(user_id, conversation_id) 41 | assert tree.settings.API_KEYS["openai_api_key"] == os.environ.get("OPENAI_API_KEY") 42 | assert tree.settings.WCD_URL == os.environ.get("WCD_URL") 43 | assert tree.settings.WCD_API_KEY == os.environ.get("WCD_API_KEY") 44 | 45 | # create a new user with a new config 46 | user_id_2 = "test_user_config_2" 47 | conversation_id_2 = "test_conversation_config_2" 48 | 49 | new_settings = Settings() 50 | new_settings.configure( 51 | openai_api_key="new_openai_api_key", 52 | wcd_url=os.getenv("WCD_URL"), 53 | wcd_api_key=os.getenv("WCD_API_KEY"), 54 | base_model="gpt-4o-mini", 55 | base_provider="openai", 56 | complex_model="gpt-4o", 57 | complex_provider="openai", 58 | ) 59 | 60 | await user_manager.add_user_local( 61 | user_id_2, 62 | config=Config( 63 | id="new_config", 64 | name="New Config", 65 | settings=new_settings, 66 | style="Informative, polite and friendly.", 67 | agent_description="You search and query Weaviate to satisfy the user's query, providing a concise summary of the results.", 68 | end_goal=( 69 | "You have satisfied the user's query, and provided a concise summary of the results. " 70 | "Or, you have exhausted all options available, or asked the user for clarification." 71 | ), 72 | branch_initialisation="one_branch", 73 | ), 74 | ) 75 | await user_manager.initialise_tree(user_id_2, conversation_id_2, low_memory=True) 76 | 77 | # get tree manager 78 | tree_manager_2 = user_manager.users[user_id_2]["tree_manager"] 79 | 80 | # check that settings is equal to new settings 81 | assert tree_manager_2.settings.API_KEYS["openai_api_key"] == "new_openai_api_key" 82 | assert tree_manager_2.settings.WCD_URL == os.getenv("WCD_URL") 83 | assert tree_manager_2.settings.WCD_API_KEY == os.getenv("WCD_API_KEY") 84 | 85 | # check that the tree has these settings too 86 | tree_2 = await user_manager.get_tree(user_id_2, conversation_id_2) 87 | assert tree_2.settings.API_KEYS["openai_api_key"] == "new_openai_api_key" 88 | assert tree_2.settings.WCD_URL == os.getenv("WCD_URL") 89 | assert tree_2.settings.WCD_API_KEY == os.getenv("WCD_API_KEY") 90 | -------------------------------------------------------------------------------- /tests/requires_env/api/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import json 3 | from fastapi.responses import JSONResponse 4 | 5 | 6 | from elysia.api.core.log import set_log_level 7 | from elysia.api.dependencies.common import get_user_manager 8 | 9 | set_log_level("CRITICAL") 10 | 11 | from elysia.api.routes.init import initialise_tree 12 | from elysia.api.routes.utils import ( 13 | follow_up_suggestions, 14 | ) 15 | from elysia.api.routes.init import initialise_user 16 | from elysia.api.api_types import ( 17 | FollowUpSuggestionsData, 18 | InitialiseTreeData, 19 | ) 20 | 21 | 22 | def read_response(response: JSONResponse): 23 | return json.loads(response.body) 24 | 25 | 26 | class fake_websocket: 27 | results = [] 28 | 29 | async def send_json(self, data: dict): 30 | self.results.append(data) 31 | 32 | 33 | class TestUtils: 34 | 35 | @pytest.mark.asyncio 36 | async def test_follow_up_suggestions(self): 37 | user_id = "test_user_follow_up_suggestions" 38 | conversation_id = "test_conversation_follow_up_suggestions" 39 | 40 | user_manager = get_user_manager() 41 | 42 | out = await initialise_user( 43 | user_id, 44 | user_manager, 45 | ) 46 | response = read_response(out) 47 | assert response["error"] == "" 48 | 49 | out = await initialise_tree( 50 | user_id, 51 | conversation_id, 52 | InitialiseTreeData( 53 | low_memory=True, 54 | ), 55 | user_manager, 56 | ) 57 | response = read_response(out) 58 | assert response["error"] == "" 59 | 60 | out = await follow_up_suggestions( 61 | FollowUpSuggestionsData( 62 | user_id=user_id, 63 | conversation_id=conversation_id, 64 | ), 65 | user_manager, 66 | ) 67 | 68 | response = read_response(out) 69 | assert response["error"] == "" 70 | 71 | 72 | if __name__ == "__main__": 73 | import asyncio 74 | 75 | asyncio.run(TestUtils().test_follow_up_suggestions()) 76 | -------------------------------------------------------------------------------- /tests/requires_env/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from elysia.util.client import ClientManager 3 | from elysia.api.dependencies.common import get_user_manager 4 | from weaviate.util import generate_uuid5 5 | from weaviate.classes.query import Filter 6 | import os 7 | from pathlib import Path 8 | from dotenv import load_dotenv 9 | 10 | # Load environment variables from .env file 11 | load_dotenv(override=True) 12 | 13 | 14 | def get_frontend_config_file_paths() -> Path: 15 | elysia_package_dir = Path(__file__).parent.parent.parent # Gets to elysia/ 16 | config_dir = elysia_package_dir / "elysia" / "api" / "user_configs" 17 | config_files = os.listdir(config_dir) 18 | return [ 19 | f"{config_dir}/{c}" 20 | for c in config_files 21 | if c.startswith("frontend_config_test_") 22 | ] 23 | 24 | 25 | def pytest_collection_modifyitems(config, items): 26 | """Skip tests if required environment variables are missing.""" 27 | if ( 28 | ("WCD_URL" not in os.environ and "WEAVIATE_URL" not in os.environ) 29 | or ("WCD_API_KEY" not in os.environ and "WEAVIATE_API_KEY" not in os.environ) 30 | or "OPENAI_API_KEY" not in os.environ 31 | or "OPENROUTER_API_KEY" not in os.environ 32 | ): 33 | skip_marker = pytest.mark.skip( 34 | reason="Missing required database environment variables" 35 | ) 36 | for item in items: 37 | item.add_marker(skip_marker) 38 | 39 | 40 | @pytest.fixture(scope="session", autouse=True) 41 | def cleanup_configs(request): 42 | yield 43 | 44 | client_manager = ClientManager() 45 | 46 | if not client_manager.is_client: 47 | return 48 | 49 | # check for local frontend configs 50 | config_files = get_frontend_config_file_paths() 51 | for config_file in config_files: 52 | if os.path.exists(config_file): 53 | os.remove(config_file) 54 | 55 | # check for weaviate configs 56 | with client_manager.connect_to_client() as client: 57 | if client.collections.exists("ELYSIA_CONFIG__"): 58 | collection = client.collections.get("ELYSIA_CONFIG__") 59 | test_configs_response = collection.query.fetch_objects( 60 | limit=1000, 61 | filters=Filter.all_of( 62 | [ 63 | Filter.by_property("user_id").like("test_*"), 64 | Filter.by_property("config_id").like("test_*"), 65 | ] 66 | ), 67 | ) 68 | for config in test_configs_response.objects: 69 | collection.data.delete_by_id(config.uuid) 70 | 71 | client_manager.client.close() 72 | 73 | 74 | @pytest.fixture(scope="session", autouse=True) 75 | def cleanup_collections(request): 76 | yield 77 | 78 | client_manager = ClientManager() 79 | 80 | if not client_manager.is_client: 81 | return 82 | 83 | # check for weaviate configs 84 | with client_manager.connect_to_client() as client: 85 | for collection_name in client.collections.list_all(): 86 | if collection_name.startswith("Test_ELYSIA_"): 87 | client.collections.delete(collection_name) 88 | elif collection_name.startswith("ELYSIA_Test_"): 89 | client.collections.delete(collection_name) 90 | 91 | client_manager.client.close() 92 | 93 | 94 | @pytest.fixture(scope="session", autouse=True) 95 | def cleanup_feedbacks(request): 96 | yield 97 | 98 | client_manager = ClientManager() 99 | 100 | if not client_manager.is_client: 101 | return 102 | 103 | # check for weaviate configs 104 | with client_manager.connect_to_client() as client: 105 | if client.collections.exists("ELYSIA_FEEDBACK__"): 106 | collection = client.collections.get("ELYSIA_FEEDBACK__") 107 | test_feedback_response = collection.query.fetch_objects( 108 | limit=1000, 109 | filters=Filter.by_property("user_id").like("test_*"), 110 | ) 111 | for feedback in test_feedback_response.objects: 112 | collection.data.delete_by_id(feedback.uuid) 113 | 114 | client_manager.client.close() 115 | -------------------------------------------------------------------------------- /tests/requires_env/general/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | from elysia.util.dummy_adapter import DummyAdapter 4 | from dspy import configure, ChatAdapter 5 | import dspy 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def event_loop(): 10 | try: 11 | loop = asyncio.get_running_loop() 12 | except RuntimeError: 13 | loop = asyncio.new_event_loop() 14 | try: 15 | yield loop 16 | finally: 17 | loop.close() 18 | 19 | 20 | @pytest.fixture(autouse=True, scope="module") 21 | def use_dummy_adapter(): 22 | 23 | prev_adapter = dspy.settings.adapter 24 | configure(adapter=DummyAdapter()) 25 | yield 26 | configure(adapter=prev_adapter) 27 | -------------------------------------------------------------------------------- /tests/requires_env/general/test_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from elysia.util.client import ClientManager 6 | 7 | 8 | @pytest.mark.asyncio 9 | async def test_client_manager_starts(): 10 | try: 11 | client_manager = ClientManager() 12 | finally: 13 | await client_manager.close_clients() 14 | 15 | 16 | @pytest.mark.asyncio 17 | async def test_client_manager_starts_with_env_vars(): 18 | try: 19 | client_manager = ClientManager() 20 | assert client_manager.wcd_url == os.getenv("WCD_URL") 21 | assert client_manager.wcd_api_key == os.getenv("WCD_API_KEY") 22 | finally: 23 | await client_manager.close_clients() 24 | 25 | 26 | @pytest.mark.asyncio 27 | async def test_client_manager_starts_with_api_keys(): 28 | try: 29 | client_manager = ClientManager() 30 | 31 | if "OPENAI_API_KEY" in os.environ: 32 | assert "X-OpenAI-Api-Key" in client_manager.headers 33 | assert client_manager.headers["X-OpenAI-Api-Key"] == os.getenv( 34 | "OPENAI_API_KEY" 35 | ) 36 | finally: 37 | await client_manager.close_clients() 38 | 39 | 40 | @pytest.mark.asyncio 41 | async def test_clients_are_connected(): 42 | try: 43 | client_manager = ClientManager() 44 | await client_manager.start_clients() 45 | assert client_manager.client.is_ready() 46 | finally: 47 | await client_manager.close_clients() 48 | 49 | 50 | @pytest.mark.asyncio 51 | async def test_sync_client_connects(): 52 | try: 53 | client_manager = ClientManager() 54 | with client_manager.connect_to_client() as client: 55 | assert client_manager.sync_in_use_counter == 1 56 | assert client_manager.sync_in_use_counter == 0 57 | finally: 58 | await client_manager.close_clients() 59 | 60 | 61 | @pytest.mark.asyncio 62 | async def test_async_client_connects(): 63 | try: 64 | client_manager = ClientManager() 65 | async with client_manager.connect_to_async_client() as client: 66 | assert client_manager.async_in_use_counter == 1 67 | pass 68 | assert client_manager.async_in_use_counter == 0 69 | finally: 70 | await client_manager.close_clients() 71 | -------------------------------------------------------------------------------- /tests/requires_env/general/test_save_load.py: -------------------------------------------------------------------------------- 1 | from elysia.tree.tree import Tree 2 | from elysia.tree.util import get_saved_trees_weaviate 3 | 4 | from elysia.util.client import ClientManager 5 | 6 | import pytest 7 | 8 | 9 | @pytest.mark.asyncio 10 | async def test_save_load_weaviate(): 11 | 12 | try: 13 | tree = Tree( 14 | user_id="test_save_load_user", 15 | conversation_id="test_save_load_conversation", 16 | low_memory=True, 17 | style="This is a test style!", 18 | agent_description="This is a test agent description!", 19 | end_goal="This is a test end goal!", 20 | ) 21 | tree.tree_data.environment.hidden_environment["Example Entry"] = ( 22 | "This is an example!" 23 | ) 24 | 25 | client_manager = ClientManager() 26 | 27 | # save the tree to weaviate 28 | await tree.export_to_weaviate( 29 | collection_name="Test_ELYSIA_save_load_collection", 30 | client_manager=client_manager, 31 | ) 32 | 33 | # load the tree from weaviate 34 | loaded_tree = await Tree.import_from_weaviate( 35 | collection_name="Test_ELYSIA_save_load_collection", 36 | conversation_id=tree.conversation_id, 37 | client_manager=client_manager, 38 | ) 39 | 40 | assert tree.user_id == loaded_tree.user_id 41 | assert tree.conversation_id == loaded_tree.conversation_id 42 | assert tree.low_memory == loaded_tree.low_memory 43 | assert tree.tree_data.atlas.style == loaded_tree.tree_data.atlas.style 44 | assert ( 45 | tree.tree_data.atlas.agent_description 46 | == loaded_tree.tree_data.atlas.agent_description 47 | ) 48 | assert tree.tree_data.atlas.end_goal == loaded_tree.tree_data.atlas.end_goal 49 | assert "Example Entry" in loaded_tree.tree_data.environment.hidden_environment 50 | assert ( 51 | tree.tree_data.environment.hidden_environment["Example Entry"] 52 | == loaded_tree.tree_data.environment.hidden_environment["Example Entry"] 53 | ) 54 | 55 | saved_trees = await get_saved_trees_weaviate( 56 | collection_name="Test_ELYSIA_save_load_collection", 57 | client_manager=client_manager, 58 | ) 59 | 60 | assert tree.conversation_id in saved_trees 61 | 62 | finally: 63 | if client_manager.client.collections.exists("Test_ELYSIA_save_load_collection"): 64 | client_manager.client.collections.delete("Test_ELYSIA_save_load_collection") 65 | 66 | await client_manager.close_clients() 67 | -------------------------------------------------------------------------------- /tests/requires_env/general/test_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from elysia.config import Settings, configure 4 | 5 | from elysia.config import settings as global_settings 6 | from elysia.config import reset_settings 7 | 8 | from elysia.tree import Tree 9 | 10 | 11 | def test_smart_setup(): 12 | """ 13 | Test that the smart setup is correct 14 | """ 15 | settings = Settings.from_smart_setup() 16 | assert settings.BASE_MODEL is not None 17 | assert settings.COMPLEX_MODEL is not None 18 | assert settings.BASE_PROVIDER is not None 19 | assert settings.COMPLEX_PROVIDER is not None 20 | -------------------------------------------------------------------------------- /tests/requires_env/llm/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | 4 | 5 | @pytest.fixture(scope="session") 6 | def event_loop(): 7 | try: 8 | loop = asyncio.get_running_loop() 9 | except RuntimeError: 10 | loop = asyncio.new_event_loop() 11 | yield loop 12 | loop.close() 13 | -------------------------------------------------------------------------------- /tests/requires_env/llm/deepeval_setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import torch 4 | import transformers 5 | from deepeval.models import DeepEvalBaseLLM 6 | from dotenv import load_dotenv 7 | from transformers import AutoModelForCausalLM, AutoTokenizer 8 | 9 | load_dotenv(override=True) 10 | 11 | 12 | class CustomLlama(DeepEvalBaseLLM): 13 | def __init__(self, max_length: int = 5000): 14 | self.max_length = max_length 15 | 16 | model_4bit = AutoModelForCausalLM.from_pretrained( 17 | "meta-llama/Llama-3.2-3B-Instruct", 18 | device_map="auto", 19 | token=os.getenv("HF_TOKEN"), 20 | ) 21 | tokenizer = AutoTokenizer.from_pretrained( 22 | "meta-llama/Llama-3.2-3B-Instruct", 23 | token=os.getenv("HF_TOKEN"), 24 | ) 25 | 26 | self.model = model_4bit 27 | self.tokenizer = tokenizer 28 | 29 | def load_model(self): 30 | return self.model 31 | 32 | def generate(self, prompt: str) -> str: 33 | model = self.load_model() 34 | 35 | pipeline = transformers.pipeline( 36 | "text-generation", 37 | model=model, 38 | tokenizer=self.tokenizer, 39 | use_cache=True, 40 | device_map="auto", 41 | max_length=self.max_length, 42 | do_sample=True, 43 | top_k=5, 44 | num_return_sequences=1, 45 | eos_token_id=self.tokenizer.eos_token_id, 46 | pad_token_id=self.tokenizer.eos_token_id, 47 | ) 48 | 49 | return pipeline(prompt) 50 | 51 | async def a_generate(self, prompt: str) -> str: 52 | return self.generate(prompt) 53 | 54 | def get_model_name(self): 55 | return "Llama-3 8B" 56 | 57 | 58 | # if __name__ == "__main__": 59 | # llm = CustomLlama() 60 | # print(llm.generate("Hello, how are you?")) 61 | 62 | # from deepeval.metrics import AnswerRelevancyMetric 63 | 64 | # metric = AnswerRelevancyMetric(model=llm) 65 | # metric.measure( 66 | # model=llm, 67 | # data=data, 68 | # ) 69 | -------------------------------------------------------------------------------- /tests/requires_env/llm/test_customisations.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from deepeval import evaluate, metrics 3 | from deepeval.test_case import LLMTestCase, LLMTestCaseParams 4 | 5 | from elysia import Tree, Settings, tool 6 | 7 | 8 | def test_tree_no_client(): 9 | 10 | settings = Settings.from_smart_setup() 11 | settings.WCD_URL = "" 12 | settings.WCD_API_KEY = "" 13 | 14 | tree = Tree( 15 | low_memory=False, 16 | branch_initialisation="one_branch", 17 | settings=settings, 18 | ) 19 | 20 | response, objects = tree.run( 21 | "What was the most recent message in the messages collection?", 22 | collection_names=["Test_ELYSIA_Messages_collection"], 23 | ) 24 | 25 | metric = metrics.GEval( 26 | name="Judge of agent being unable to connect to Weaviate", 27 | criteria=""" 28 | The response should inform the user that the agent is unable to connect to Weaviate, 29 | and to set the WCD_URL and WCD_API_KEY in the settings. 30 | OR, the response should inform the user to analyse the data. 31 | """, 32 | evaluation_params=[ 33 | LLMTestCaseParams.INPUT, 34 | LLMTestCaseParams.ACTUAL_OUTPUT, 35 | LLMTestCaseParams.EXPECTED_OUTPUT, 36 | ], 37 | ) 38 | 39 | test_case = LLMTestCase( 40 | input="What was the most recent message in the messages collection?", 41 | actual_output=response, 42 | expected_output=""" 43 | The agent is unable to connect to Weaviate, 44 | and to set the WCD_URL and WCD_API_KEY in the settings. 45 | OR, the response should inform the user to analyse the data. 46 | """, 47 | ) 48 | 49 | res = evaluate(test_cases=[test_case], metrics=[metric]) 50 | for test_case in res.test_results: 51 | assert test_case.success, test_case.metrics_data[0].reason 52 | 53 | 54 | @pytest.mark.asyncio 55 | async def test_tool_decorator(): 56 | @tool 57 | async def add_two_numbers(x: int, y: int) -> int: 58 | return x + y 59 | 60 | tree = Tree( 61 | low_memory=False, 62 | branch_initialisation="one_branch", 63 | settings=Settings.from_smart_setup(), 64 | ) 65 | tree.add_tool(add_two_numbers) 66 | 67 | assert "add_two_numbers" in tree.tools 68 | 69 | prompt = "What is the sum of 1123213 and 2328942390843209?" 70 | response, objects = tree.run(prompt) 71 | 72 | assert "2328942391966422" in response 73 | 74 | found = False 75 | for action in tree.actions_called[prompt]: 76 | if ( 77 | 1123213 in action["inputs"].values() 78 | and 2328942390843209 in action["inputs"].values() 79 | ): 80 | found = True 81 | break 82 | 83 | assert found 84 | 85 | 86 | @pytest.mark.asyncio 87 | async def test_tool_decorator_with_args(): 88 | 89 | tree = Tree( 90 | low_memory=False, 91 | branch_initialisation="one_branch", 92 | settings=Settings.from_smart_setup(), 93 | ) 94 | 95 | @tool(tree=tree) 96 | async def add_two_numbers(x: int, y: int) -> int: 97 | return x + y 98 | 99 | assert "add_two_numbers" in tree.tools 100 | 101 | prompt = "What is the sum of 1123213 and 2328942390843209?" 102 | response, objects = tree.run(prompt) 103 | 104 | assert "2328942391966422" in response 105 | 106 | found = False 107 | for action in tree.actions_called[prompt]: 108 | if ( 109 | 1123213 in action["inputs"].values() 110 | and 2328942390843209 in action["inputs"].values() 111 | ): 112 | found = True 113 | break 114 | 115 | assert found 116 | 117 | tree = Tree() 118 | 119 | @tool(tree=tree, status="Adding two numbers", end=True) 120 | async def add_two_numbers2(x: int, y: int) -> int: 121 | """ 122 | This function adds two numbers together. 123 | """ 124 | return x + y 125 | -------------------------------------------------------------------------------- /tests/requires_env/llm/test_retrieval_accuracy.py: -------------------------------------------------------------------------------- 1 | """ 2 | More complex testing of retrieval accuracy. 3 | I.e. testing whether the correct data is retrieved for a given prompt. 4 | This also includes testing the constructed query code contains the correct fields/filters etc. 5 | """ 6 | 7 | # TODO 8 | -------------------------------------------------------------------------------- /tests/requires_env/llm/test_self_healing.py: -------------------------------------------------------------------------------- 1 | # self-healing errors can be tested by making a custom tool that returns an error only on thef irst 2 | # and then returns a result on the second call 3 | from elysia import Tree, Tool, Error 4 | from elysia.tree.objects import TreeData 5 | from elysia.util.client import ClientManager 6 | import dspy 7 | 8 | 9 | class TestSelfHealing(Tool): 10 | 11 | def __init__(self, **kwargs): 12 | super().__init__( 13 | name="test_self_healing", 14 | description="Test self-healing", 15 | status="healing...", 16 | inputs={}, 17 | end=False, 18 | **kwargs, 19 | ) 20 | self.first_call = True 21 | 22 | async def __call__( 23 | self, 24 | tree_data: TreeData, 25 | inputs: dict, 26 | base_lm: dspy.LM, 27 | complex_lm: dspy.LM, 28 | client_manager: ClientManager, 29 | **kwargs, 30 | ): 31 | if self.first_call: 32 | yield Error("The first call of this tool _always_ fails.") 33 | self.first_call = False 34 | 35 | 36 | def test_self_healing(): 37 | tree = Tree() 38 | tree.add_tool(TestSelfHealing) 39 | 40 | tree("Call the self-healing tool") 41 | assert "error" in tree.tree_data.tasks_completed[0]["task"][0] 42 | assert tree.tree_data.tasks_completed[0]["task"][0]["error"] 43 | assert "error" not in tree.tree_data.tasks_completed[0]["task"][1] 44 | --------------------------------------------------------------------------------