Compare commits

...

144 Commits

Author SHA1 Message Date
Taranjeet Singh f582d70031 release: bump version to 0.0.54 (#575) 2023-09-07 05:43:18 +05:30
cachho 1ac8aef4de docs: update docstrings (#565) 2023-09-07 05:34:44 +05:30
Dev Khant 4754372fcd Add flask and twilio to extras (#559) 2023-09-06 04:29:50 +05:30
cachho eac85779eb chore: linting (#556)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-06 04:29:29 +05:30
cachho b0d8711b65 fix: Elasticsearch - use correct class attributes (#566) 2023-09-06 04:28:40 +05:30
cachho f0844ed923 fix: pin gpt4all version (#563) 2023-09-06 04:27:38 +05:30
Dev Khant 129242534d Lint and formatting fixes (#554)
Co-authored-by: cachho <admin@ch-webdev.com>
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-06 04:24:19 +05:30
Taranjeet Singh 6481b555b4 fix: typo in poe bot docs (#569) 2023-09-06 04:19:44 +05:30
Taranjeet Singh 794e51494e fix: update Poe bot docs (#568) 2023-09-06 02:32:49 +05:30
cachho 3059e96041 feat: Slack bot (#469)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-05 13:48:52 +05:30
cachho bd595f84e8 feat: csv loader (#470)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-05 13:48:03 +05:30
cachho 344e7470f6 refactor: classes and configs (#528) 2023-09-05 13:42:58 +05:30
Taranjeet Singh 387b042a49 feat: Bump version to 0.0.53 (#552) 2023-09-05 09:04:37 +05:30
Taranjeet Singh b3837572be fix: Add upgrade for pip install (#551) 2023-09-05 09:02:48 +05:30
Taranjeet Singh d4e6462e4d feat: Make poe bot run as an app instead of server. (#550) 2023-09-05 08:59:41 +05:30
omahs 60d5daaaf5 docs: fix typos (#548) 2023-09-05 03:45:53 +05:30
sw8fbar 3e66ddf69a feat: where filter in vector database (#518) 2023-09-05 02:19:59 +05:30
Tarun Jain 202fd2d5b6 Add community showcase details for Embedchain in one shot (#544)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-05 00:20:20 +05:30
wangJm eecdbc5e06 Upgrade the chromadb version to 0.4.8 and open its settings configuration. (#517) 2023-09-04 12:01:08 +05:30
cachho 433c4157e0 chore: linting (#543) 2023-09-04 01:48:50 +05:30
cachho 8be8990507 feat: discord bot (#465) 2023-09-04 01:23:01 +05:30
Taranjeet Singh 2cfeb5ed80 bump version to 0.0.52 (#542) 2023-09-04 01:21:45 +05:30
cachho 0d4ad07d7b Feat/serialize deserialize (#508)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-04 01:20:18 +05:30
Qihang 2aa25a5169 Bump up LangChain version and bugfix (#536)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-04 01:11:09 +05:30
Joseph Chancey c07fbc07b1 added contribution img to readme using contrib.rocks embedding (#495)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-04 01:09:14 +05:30
Taranjeet Singh 12eb16d60b bump version to 0.0.51 (#540) 2023-09-04 01:04:10 +05:30
Dev Khant ec9f454ad1 System prompt at App level (#484)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-04 00:55:43 +05:30
Ikko Eltociear Ashimine 9f1f17a611 Fix typo in README.md (#513)
Co-authored-by: cachho <admin@ch-webdev.com>
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-04 00:54:18 +05:30
aryankhanna475 94369657ef Showcase Update 01-09 (#527)
Co-authored-by: Sahil Kumar Yadav <sahilyadav902@gmail.com>
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-04 00:52:35 +05:30
Taranjeet Singh 490d7db601 Bump version to 0.0.50 (#530) 2023-09-02 05:56:06 +05:30
Taranjeet Singh ea18b80f90 fix: update chroma init for host & port (#529) 2023-09-02 05:52:10 +05:30
Deshraj Yadav 70077f4e46 Update package version to 0.0.49 (#515) 2023-08-30 11:25:46 -07:00
cachho 85106c7c7e fix: remove stale code (#514) 2023-08-30 11:19:41 -07:00
Taranjeet Singh 1b19d0d19c bump version to 0.0.48 (#503) 2023-08-29 00:31:50 +05:30
Taranjeet Singh 13f01e399c fix: add tiktoken as required dependency in pyproject (#502) 2023-08-29 00:29:12 +05:30
Taranjeet Singh 261e2d088c Bump version to 0.0.47 (#501) 2023-08-29 00:00:35 +05:30
Taranjeet Singh b0ae3e95c7 fix: update poe bot creation docs (#500) 2023-08-28 23:59:34 +05:30
cachho fc633dadeb feat: poe bot (#492)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-08-28 23:47:01 +05:30
Taranjeet Singh aafb334916 fix: improve CTA of book meeting (#496) 2023-08-28 08:19:47 +05:30
Taranjeet Singh 04d851e802 fix: update discord link (#494) 2023-08-28 05:33:03 +05:30
Taranjeet Singh de31c63dac feat: Add cal.com link for feedback (#493) 2023-08-28 05:12:54 +05:30
Deshraj Yadav c068f58543 [version]: bump package version to 0.0.46 2023-08-27 12:24:37 -07:00
Deshraj Yadav 70df373807 [feat]: add support for sending anonymous user_id in telemetry (#491) 2023-08-28 00:53:42 +05:30
Deshraj Yadav 4388f6bfc2 [bug-fix] fix issue related to bot memory when using multiple bots at the same time (#486) 2023-08-25 21:59:39 -07:00
Taranjeet Singh d0956a0dc1 Bump version to 0.0.45 (#479) 2023-08-25 03:22:04 +05:30
cachho ccf515cadd Fix/use poetry for build (#478) 2023-08-25 03:20:57 +05:30
Taranjeet Singh a6e4235bb0 fix: update docs (#477) 2023-08-25 03:16:15 +05:30
Taranjeet Singh 9ba408086e Bump version to 0.0.44 (#476) 2023-08-25 03:12:22 +05:30
cachho bbb3bca1c7 fix: python version string (#475) 2023-08-25 03:10:48 +05:30
Taranjeet Singh 564036a166 Bump version to 0.0.43 (#474) 2023-08-25 03:05:27 +05:30
cachho abf99ce5ea chore: add release workflow (#397) 2023-08-25 03:04:07 +05:30
Taranjeet Singh f0f5c34acb Bump version to 0.0.42 (#473) 2023-08-25 02:55:29 +05:30
cachho ed319531bf fix: dependencies (#416) 2023-08-25 02:53:25 +05:30
Dev Khant d8d0e0e5d1 Add whatsapp bot poetry dependencies (#466) 2023-08-23 10:15:25 -07:00
Girish S 27c91bbd2d Create documentation_issue.yml as Github Issue Template for documentation (#460) 2023-08-20 13:36:44 +05:30
Taranjeet Singh f76d9740c6 Bump version to 0.0.41 (#459) 2023-08-20 02:44:22 +05:30
Deshraj Yadav f29443a0fc [feat] Add support for creating whatsapp bot using embedchain (#458) 2023-08-20 02:42:48 +05:30
Taranjeet Singh 35b022d6bc Add discord link (#456) 2023-08-19 11:05:00 +05:30
Taranjeet Singh 0dd1faf57f Github: Improve issue template. (#455) 2023-08-19 10:31:05 +05:30
Taranjeet Singh b57f096b27 Bump version to 0.0.40 (#454) 2023-08-17 01:53:03 +05:30
cachho 4c8876f032 feat: add method - detect format / data_type (#380) 2023-08-17 01:48:24 +05:30
Taranjeet Singh f92e890aa1 Bump version to 0.0.39 (#453) 2023-08-17 01:39:22 +05:30
cachho 849de5e8ab feat: system prompt (#448) 2023-08-17 01:27:01 +05:30
Dev Khant 7585bc557b Add guide links to readme (#451) 2023-08-16 22:55:13 +05:30
cachho 4021d93168 chore: linting (#449) 2023-08-16 04:57:19 +05:30
Jonas 28e06be26f fix: reset destroys app (#319)
Co-authored-by: cachho <admin@ch-webdev.com>
2023-08-15 05:22:02 +05:30
cachho 39861ec1e8 fix: add telemetry to add_local (#437) 2023-08-15 03:12:10 +05:30
Sahil Kumar Yadav e3ae84b80d add: WhatsApp, Slack and Telegram bots (#438) 2023-08-15 03:10:25 +05:30
Taranjeet Singh 09c02954ba Bump version to 0.0.38 (#442) 2023-08-15 03:01:11 +05:30
cachho 66b661660b feat: session id for telemetry (#440) 2023-08-15 02:58:09 +05:30
cachho c26559a2d3 fix: notion install error message (#439) 2023-08-15 02:57:23 +05:30
Taranjeet Singh c5da46f8b0 update: discord bot docs (#435) 2023-08-12 06:00:21 +05:30
Sahil Kumar Yadav 0b72269e18 add: support for discord bot (#412) 2023-08-12 05:52:27 +05:30
cachho a232d1b779 refactor: do not instantiate all loaders (#418) 2023-08-12 05:32:40 +05:30
Taranjeet Singh 3cab4415b7 Bump version to 0.0.37 (#431) 2023-08-12 05:07:51 +05:30
Taranjeet Singh d494d99c06 docs: update variable name (#430) 2023-08-12 04:58:37 +05:30
cachho 163f437582 feat: anonymous telemetry (#423) 2023-08-12 04:57:11 +05:30
cachho 1e0d967bb5 chore: linting (#428) 2023-08-10 23:42:38 -07:00
Sahil Kumar Yadav a86deb2675 add: API server(#422)
Add an example of api server so that devs can quickly get up a bot running along with its api
2023-08-11 11:30:51 +05:30
Taranjeet Singh d51c508b40 Bump version to 0.0.36 (#426) 2023-08-11 09:50:39 +05:30
aryankhanna475 af8b3081fa feat: Update showcase section in the docs (#421) 2023-08-11 09:44:45 +05:30
Taranjeet Singh 1dbe7daac1 fix: Update embedding field name for Elastiscearch mapping (#425) 2023-08-11 09:43:52 +05:30
Taranjeet Singh e56f91a239 Bump version to 0.0.35 (#424) 2023-08-11 09:29:31 +05:30
Prashant Chaudhary 0179141b2e feat: add support for Elastcisearch as vector data source (#402) 2023-08-11 09:23:56 +05:30
cachho f0abfea55d chore: linting (#414) 2023-08-11 01:53:42 +05:30
Taranjeet Singh 77e223be52 Bump version to 0.0.34 (#420) 2023-08-10 04:48:34 +05:30
Taranjeet Singh c96df72cd0 Fix: lazy load Notion loader (#419) 2023-08-10 04:44:02 +05:30
cachho ce6eb39009 feat: notion loader (#405) 2023-08-09 13:15:22 +05:30
Jonas eeac84e2d9 feat: collection name everywhere (#310)
Co-authored-by: cachho <admin@ch-webdev.com>
2023-08-09 13:08:35 +05:30
Taranjeet Singh 1ee1e671d1 Bump version to 0.0.33 (#411) 2023-08-09 12:59:45 +05:30
cachho 2ef7c0b736 fix: escape pysqlite swapping (#410) 2023-08-09 12:54:41 +05:30
aryankhanna475 f2b563e42a Additions to the community showcase (#401)
Co-authored-by: Sahil Kumar Yadav <sahilyadav902@gmail.com>
2023-08-09 12:39:36 +05:30
Taranjeet Singh 7a718643a3 bump version to 0.0.32 (#409) 2023-08-09 12:28:35 +05:30
Taranjeet Singh 1f0f0c93b7 fix: Pass deployment name as param for azure api (#406) 2023-08-09 12:25:26 +05:30
Taranjeet Singh 030e3521a9 Bump version to 0.0.31 (#408) 2023-08-09 12:17:57 +05:30
cachho fdf5d1928d test: added chunker unit tests (#325) 2023-08-09 09:12:30 +05:30
cachho 65011a67d4 fix: is readable - zero division error (#383) 2023-08-09 09:06:26 +05:30
Sahil Kumar Yadav ec09a8a6fc example: embedchain playground (#384) 2023-08-08 08:43:05 -07:00
cachho 5e94980aaa fix: no logging in pysqlite replacement (#378) 2023-07-27 07:00:01 -07:00
aryankhanna475 8b619756b6 update: docs showcase (#377) 2023-07-27 06:58:22 -07:00
cachho 35b43edb20 fix: typo in readme example (#373) 2023-07-27 06:56:48 -07:00
cachho 02cbde2fc1 docs: fix argument out of order (#374) 2023-07-27 06:56:18 -07:00
cachho a868fce036 fix: remove debug logging (#379) 2023-07-27 06:55:00 -07:00
cachho 079e35b205 fix: chroma pysqlite version (#350) 2023-07-27 13:12:27 +05:30
Deshraj Yadav 8c91b75b98 [Feature]: Add support for azure openai model (#372) 2023-07-27 13:03:32 +05:30
cachho 55bfd7cafe refactor: loader chunker typing (#324) 2023-07-26 23:14:57 +05:30
cachho a8552686b4 docs: add back query config (#365) 2023-07-26 23:13:56 +05:30
Alessandro Panzieri 12a2f78dcb add "d" on "Embedchain" in citation section title (#369) 2023-07-26 22:57:01 +05:30
aaishikdutta cce6d5ddab fix: Personapp not working with config (#368) 2023-07-26 22:04:11 +05:30
cachho 088346c4fc docs: fix template (#364) 2023-07-25 00:46:50 -07:00
aaishikdutta 7fa7b9e199 Update discord badge in readme.md (#361) 2023-07-24 09:19:54 -07:00
aaishikdutta cac15c147f added fix for documentation (#362) 2023-07-24 01:18:37 -07:00
aaishikdutta c54dd1e7bb Fixed test case for chroma db (#358) 2023-07-22 16:17:01 -07:00
aaishikdutta c9c56a4b26 fixed dry_run not working in PersonApp (#357) 2023-07-21 22:59:20 -07:00
Taranjeet Singh acbdb800d3 Bump version to 0.0.30 (#355) 2023-07-22 07:44:38 +05:30
Taranjeet Singh 49711c92b7 bug: fix online bug in chat endpoint (#354) 2023-07-22 07:42:17 +05:30
aaishikdutta c4797eb121 fix: fix PersonOpenSourceApp query error (#353) 2023-07-22 00:14:07 +05:30
Taranjeet Singh bea4e9e6d1 Bump version to 0.0.29 (#349) 2023-07-21 13:10:21 +05:30
Taranjeet Singh 9687ec2c1a fix: init metadata as empty (#348) 2023-07-21 13:08:46 +05:30
Taranjeet Singh 09da4a3002 Fix package version in setup.py (#345) 2023-07-21 08:21:30 +05:30
Taranjeet Singh 93004306f2 Bump version to 0.0.28 (#344) 2023-07-21 08:16:40 +05:30
juaneloDev aed894246f fix: add metadata in add and add_local (#343) 2023-07-21 08:10:01 +05:30
Taranjeet Singh fa63a16591 bug: Chroma needs diff client as per settings (#342) 2023-07-21 06:47:01 +05:30
Taranjeet Singh 40643663cb Bump chroma version to 0.4 in setup.py (#341) 2023-07-21 03:28:24 +05:30
Candido Sales Gomes d590e4423b update: chroma v0.4.0 (#330) 2023-07-21 02:47:53 +05:30
Taranjeet Singh cdbf75e0ec Bump version to 0.0.27 (#337) 2023-07-21 00:06:54 +05:30
Taranjeet Singh 8216e05784 Bump version to 0.0.26 (#333) 2023-07-20 12:43:44 +05:30
Deshraj Yadav 2d4c51aa16 Fix error message on missing replicate api token (#332) 2023-07-20 12:42:27 +05:30
Deshraj Yadav cc43846d42 [feat]: add support for llama2 model (#331) 2023-07-20 12:31:37 +05:30
Sahil Kumar Yadav 3bdec3b71a fix: ValueError: ChromaDb cannot be instantiated without an embedding function (#312) 2023-07-20 12:22:41 +05:30
cachho a22a435690 docs: explain AddConfig (#327) 2023-07-20 12:03:38 +05:30
cachho a681d47bce fix: docs_site use chunker config implementation (#326) 2023-07-20 11:59:59 +05:30
aaishikdutta 4bb06147c1 [BREAKING CHANGE] moved dry run into query and chat (#329)
Co-authored-by: Aaishik Dutta <aaishikdutta@Aaishiks-MacBook-Pro.local>
2023-07-20 11:55:41 +05:30
cachho 6b61b7e9c1 perf: don't instantiate every class in dict (#328) 2023-07-20 11:52:44 +05:30
cachho 91033c7221 chore: update pr template (#323) 2023-07-19 01:25:17 -07:00
Taranjeet Singh ab9f005885 docs: add provider, embedding function (#318) 2023-07-19 05:53:28 +05:30
cachho 3da5724853 feat: filter sitemap (#304) 2023-07-19 05:36:39 +05:30
aaishikdutta c12362486f feat: added data format to metadata internally (#314) 2023-07-19 05:35:43 +05:30
cachho d16eafae05 fix: format lint (#316) 2023-07-19 02:22:09 +05:30
cachho bb1fbba161 fix: add isort dev dependency (#315) 2023-07-19 02:17:54 +05:30
cachho adb7206639 feat: add new custom app (#313) 2023-07-19 00:54:23 +05:30
cachho 96143ac496 docs: app config instead of init config (#308) 2023-07-18 12:46:06 +05:30
cachho 1df804e7df fix: delete Apps (#307) 2023-07-18 12:40:37 +05:30
cachho 0ea278f633 refactor: app design concept (#305) 2023-07-17 16:20:26 -07:00
208 changed files with 18898 additions and 1167 deletions
+7
View File
@@ -1 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: 1-on-1 Session
url: https://cal.com/taranjeetio/ec
about: Speak directly with Taranjeet, the founder, to discuss issues, share feedback, or explore improvements for Embedchain
- name: Discord
url: https://discord.gg/6PzXDgEjG5
about: General community discussions
@@ -0,0 +1,11 @@
name: Documentation
description: Report an issue related to the Embedchain docs.
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
body:
- type: textarea
attributes:
label: "Issue with current documentation:"
description: >
Please make sure to leave a reference to the document/code you're
referring to.
+2 -11
View File
@@ -1,8 +1,9 @@
name: 🚀 Feature request
description: Submit a proposal/request for a new embedchain feature
description: Submit a proposal/request for a new Embedchain feature
body:
- type: textarea
id: feature-request
attributes:
label: 🚀 The feature
description: >
@@ -16,16 +17,6 @@ body:
Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too.
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: >
A description of any alternative solutions or features you've considered, if any.
- type: textarea
attributes:
label: Additional context
description: >
Add any other context or screenshots about the feature request.
- type: markdown
attributes:
value: >
+5 -2
View File
@@ -11,14 +11,17 @@ Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Refactor (does not change functionality, e.g. code style improvements, linting)
- [ ] Documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
- [ ] Test A
- [ ] Test B
Please delete options that are not relevant.
- [ ] Unit Test
- [ ] Test Script (please provide)
## Checklist:
+32 -16
View File
@@ -1,24 +1,40 @@
name: cd
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
on:
release:
types:
- published
permissions:
id-token: write
contents: read
types: [published] # This will trigger the workflow when you create a new release
jobs:
publish_to_pypi:
name: publish to pypi on new release
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-latest
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- uses: actions/checkout@v3
- uses: JRubics/poetry-publish@v1.16
name: Build and publish to PyPI
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}
ignore_dev_requirements: "yes"
repository_url: https://upload.pypi.org/legacy/
repository_name: embedchain
python-version: '3.11'
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: poetry install
- name: Build a binary wheel and a source tarball
run: poetry build
- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository_url: https://test.pypi.org/legacy/
- name: Publish distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
+26 -6
View File
@@ -1,17 +1,21 @@
# embedchain
[![PyPI](https://img.shields.io/pypi/v/embedchain)](https://pypi.org/project/embedchain/)
[![Discord](https://dcbadge.vercel.app/api/server/nhvCbCtKV?style=flat)](https://discord.gg/6PzXDgEjG5)
[![Discord](https://dcbadge.vercel.app/api/server/6PzXDgEjG5?style=flat)](https://discord.gg/CUU9FPhRNt)
[![Twitter](https://img.shields.io/twitter/follow/embedchain)](https://twitter.com/embedchain)
[![Substack](https://img.shields.io/badge/Substack-%23006f5c.svg?logo=substack)](https://embedchain.substack.com/)
[![Open in Colab](https://camo.githubusercontent.com/84f0493939e0c4de4e6dbe113251b4bfb5353e57134ffd9fcab6b8714514d4d1/68747470733a2f2f636f6c61622e72657365617263682e676f6f676c652e636f6d2f6173736574732f636f6c61622d62616467652e737667)](https://colab.research.google.com/drive/138lMWhENGeEu7Q1-6lNbNTHGLZXBBz_B?usp=sharing)
Embedchain is a framework to easily create LLM powered bots over any dataset. If you want a javascript version, check out [embedchain-js](https://github.com/embedchain/embedchainjs)
## 🤝 Schedule a 1-on-1 Session
Book a [1-on-1 Session](https://cal.com/taranjeetio/ec) with Taranjeet, the founder, to discuss any issues, provide feedback, or explore how we can improve Embedchain for you.
## 🔧 Quick install
```bash
pip install embedchain
pip install --upgrade embedchain
```
## 🔍 Demo
@@ -28,6 +32,16 @@ The documentation for embedchain can be found at [docs.embedchain.ai](https://do
Embedchain empowers you to create chatbot models similar to ChatGPT, using your own evolving dataset.
### Data Types Supported
* Youtube video
* PDF file
* Web page
* Sitemap
* Doc file
* Code documentation website loader
* Notion
### Queries
For example, you can use Embedchain to create an Elon Musk bot using the following code:
@@ -41,9 +55,9 @@ os.environ["OPENAI_API_KEY"] = "YOUR API KEY"
elon_bot = App()
# Embed online resources
elon_bot.add("web_page", "https://en.wikipedia.org/wiki/Elon_Musk")
elon_bot.add("web_page", "https://tesla.com/elon-musk")
elon_bot.add("youtube_video", "https://www.youtube.com/watch?v=MxZpaJK74Y4")
elon_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
elon_bot.add("https://tesla.com/elon-musk")
elon_bot.add("https://www.youtube.com/watch?v=MxZpaJK74Y4")
# Query the bot
elon_bot.query("How many companies does Elon Musk run?")
@@ -55,6 +69,12 @@ elon_bot.query("How many companies does Elon Musk run?")
Contributions are welcome! Please check out the issues on the repository, and feel free to open a pull request.
For more information, please see the [contributing guidelines](CONTRIBUTING.md).
For more reference, please go through [Development Guide](https://docs.embedchain.ai/contribution/dev) and [Documentation Guide](https://docs.embedchain.ai/contribution/docs).
<a href="https://github.com/embedchain/embedchain/graphs/contributors">
<img src="https://contrib.rocks/image?repo=embedchain/embedchain" />
</a>
## Citation
If you utilize this repository, please consider citing it with:
@@ -62,7 +82,7 @@ If you utilize this repository, please consider citing it with:
```
@misc{embedchain,
author = {Taranjeet Singh},
title = {Embechain: Framework to easily create LLM powered bots over any dataset},
title = {Embedchain: Framework to easily create LLM powered bots over any dataset},
year = {2023},
publisher = {GitHub},
journal = {GitHub repository},
+25
View File
@@ -0,0 +1,25 @@
---
title: '➕ Adding Data'
---
## Add Dataset
- This step assumes that you have already created an `app` instance by either using `App`, `OpenSourceApp` or `CustomApp`. We are calling our app instance as `naval_chat_bot` 🤖
- Now use `.add` method to add any dataset.
```python
# naval_chat_bot = App() or
# naval_chat_bot = OpenSourceApp()
# Embed Online Resources
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("https://nav.al/feedback")
naval_chat_bot.add("https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add(("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
```
The possible formats to add data can be found on the [Supported Data Formats](/advanced/data_types) page.
+79 -77
View File
@@ -2,12 +2,6 @@
title: '📱 App types'
---
Creating a chatbot involves 3 steps:
- ⚙️ Import the App instance
- 🗃️ Add Dataset
- 💬 Query or Chat on the dataset and get answers (Interface Types)
## App Types
We have three types of App.
@@ -16,13 +10,12 @@ We have three types of App.
```python
from embedchain import App
naval_chat_bot = App()
app = App()
```
- `App` uses OpenAI's model, so these are paid models. 💸 You will be charged for embedding model usage and LLM usage.
- `App` uses OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you have don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- `App` uses OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- `App` is opinionated. It uses the best embedding model and LLM on the market.
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
```python
@@ -30,16 +23,89 @@ import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
### Llama2App
```python
import os
from embedchain import Llama2App
os.environ['REPLICATE_API_TOKEN'] = "REPLICATE API TOKEN"
zuck_bot = Llama2App()
# Embed your data
zuck_bot.add("https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
zuck_bot.add("https://en.wikipedia.org/wiki/Mark_Zuckerberg")
# Nice, your bot is ready now. Start asking questions to your bot.
zuck_bot.query("Who is Mark Zuckerberg?")
# Answer: Mark Zuckerberg is an American internet entrepreneur and business magnate. He is the co-founder and CEO of Facebook. Born in 1984, he dropped out of Harvard University to focus on his social media platform, which has since grown to become one of the largest and most influential technology companies in the world.
# Enable web search for your bot
zuck_bot.online = True # enable internet access for the bot
zuck_bot.query("Who owns the new threads app and when it was founded?")
# Answer: Based on the context provided, the new Threads app is owned by Meta, the parent company of Facebook, Instagram, and WhatsApp.
```
- `Llama2App` uses Replicate's LLM model, so these are paid models. You can get the `REPLICATE_API_TOKEN` by registering on [their website](https://replicate.com/account).
- `Llama2App` uses OpenAI's embedding model to create embeddings for chunks. Make sure that you have an OpenAI account and an API key. If you don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
### OpenSourceApp
```python
from embedchain import OpenSourceApp
naval_chat_bot = OpenSourceApp()
app = OpenSourceApp()
```
- `OpenSourceApp` uses open source embedding and LLM model. It uses `all-MiniLM-L6-v2` from Sentence Transformers library as the embedding model and `gpt4all` as the LLM.
- Here there is no need to setup any api keys. You just need to install embedchain package and these will get automatically installed. 📦
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app. 📚
- `OpenSourceApp` is opinionated. It uses the best open source embedding model and LLM on the market.
- extra dependencies are required for this app type. Install them with `pip install embedchain[opensource]`.
### CustomApp
```python
from embedchain import CustomApp
from embedchain.config import (CustomAppConfig, ElasticsearchDBConfig,
EmbedderConfig, LlmConfig)
from embedchain.embedder.vertexai_embedder import VertexAiEmbedder
from embedchain.llm.vertex_ai_llm import VertexAiLlm
from embedchain.models import EmbeddingFunctions, Providers
from embedchain.vectordb.elasticsearch_db import Elasticsearch
# short
app = CustomApp(llm=VertexAiLlm(), db=Elasticsearch(), embedder=VertexAiEmbedder())
# with configs
app = CustomApp(
config=CustomAppConfig(log_level="INFO"),
llm=VertexAiLlm(config=LlmConfig(number_documents=5)),
db=Elasticsearch(config=ElasticsearchDBConfig(es_url="...")),
embedder=VertexAiEmbedder(config=EmbedderConfig()),
)
```
- `CustomApp` is not opinionated.
- Configuration required. It's for advanced users who want to mix and match different embedding models and LLMs.
- while it's doing that, it's still providing abstractions by allowing you to import Classes from `embedchain.llm`, `embedchain.vectordb`, and `embedchain.embedder`.
- paid and free/open source providers included.
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app. 📚
- Following providers are available for an LLM
- OPENAI
- ANTHPROPIC
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
- LLAMA2
- Following embedding functions are available for an embedding function
- OPENAI
- HUGGING_FACE
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
### PersonApp
@@ -49,7 +115,7 @@ naval_chat_bot = PersonApp("name_of_person_or_character") #Like "Yoda"
```
- `PersonApp` uses OpenAI's model, so these are paid models. 💸 You will be charged for embedding model usage and LLM usage.
- `PersonApp` uses OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you have don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- `PersonApp` uses OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
```python
@@ -57,25 +123,7 @@ import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
## Add Dataset
- This step assumes that you have already created an `app` instance by either using `App` or `OpenSourceApp`. We are calling our app instance as `naval_chat_bot` 🤖
- Now use `.add()` function to add any dataset.
```python
# naval_chat_bot = App() or
# naval_chat_bot = OpenSourceApp()
# Embed Online Resources
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("web_page", "https://nav.al/feedback")
naval_chat_bot.add("web_page", "https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
```
#### Compatibility with other apps
- If there is any other app instance in your script or app, you can change the import as
@@ -90,49 +138,3 @@ from embedchain import App as ECApp
from embedchain import OpenSourceApp as ECOSApp
from embedchain import PersonApp as ECPApp
```
## Interface Types
### Query Interface
- This interface is like a question answering bot. It takes a question and gets the answer. It does not maintain context about the previous chats.❓
- To use this, call `.query()` function to get the answer for any query.
```python
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
### Chat Interface
- This interface is chat interface where it remembers previous conversation. Right now it remembers 5 conversation by default. 💬
- To use this, call `.chat` function to get the answer for any query.
```python
print(naval_chat_bot.chat("How to be happy in life?"))
# answer: The most important trick to being happy is to realize happiness is a skill you develop and a choice you make. You choose to be happy, and then you work at it. It's just like building muscles or succeeding at your job. It's about recognizing the abundance and gifts around you at all times.
print(naval_chat_bot.chat("who is naval ravikant?"))
# answer: Naval Ravikant is an Indian-American entrepreneur and investor.
print(naval_chat_bot.chat("what did the author say about happiness?"))
# answer: The author, Naval Ravikant, believes that happiness is a choice you make and a skill you develop. He compares the mind to the body, stating that just as the body can be molded and changed, so can the mind. He emphasizes the importance of being present in the moment and not getting caught up in regrets of the past or worries about the future. By being present and grateful for where you are, you can experience true happiness.
```
### Stream Response
- You can add config to your query method to stream responses like ChatGPT does. You would require a downstream handler to render the chunk in your desirable format. Supports both OpenAI model and OpenSourceApp. 📊
- To use this, instantiate a `QueryConfig` or `ChatConfig` object with `stream=True`. Then pass it to the `.chat()` or `.query()` method. The following example iterates through the chunks and prints them as they appear.
```python
app = App()
query_config = QueryConfig(stream = True)
resp = app.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config)
for chunk in resp:
print(chunk, end="", flush=True)
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
+45 -49
View File
@@ -4,39 +4,49 @@ title: '⚙️ Custom configurations'
Embedchain is made to work out of the box. However, for advanced users we're also offering configuration options. All of these configuration options are optional and have sane defaults.
## Concept
The main `App` class is available in the following varieties: `CustomApp`, `OpenSourceApp` and `Llama2App` and `App`. The first is fully configurable, the others are opinionated in some aspects.
The `App` class has three subclasses: `llm`, `db` and `embedder`. These are the core ingredients that make up an EmbedChain app.
App plus each one of the subclasses have a `config` attribute.
You can pass a `Config` instance as an argument during initialization to persistently configure a class.
These configs can be imported from `embedchain.config`
There are `set` methods for some things that should not (only) be set at start-up, like `app.db.set_collection_name`.
## Examples
### Custom embedding function
### General
Here's the readme example with configuration options.
```python
import os
from embedchain import App
from embedchain.config import InitConfig, AddConfig, QueryConfig, ChunkerConfig
from chromadb.utils import embedding_functions
from embedchain.config import AppConfig, AddConfig, LlmConfig, ChunkerConfig
# Example: use your own embedding function
config = InitConfig(ef=embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name="text-embedding-ada-002"
))
# Example: set the log level for debugging
config = AppConfig(log_level="DEBUG")
naval_chat_bot = App(config)
# Example: specify a custom collection name
naval_chat_bot.db.set_collection_name("naval_chat_bot")
# Example: define your own chunker config for `youtube_video`
chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=100, length_function=len)
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44", AddConfig(chunker=chunker_config))
# Example: Add your chunker config to an AddConfig to actually use it
add_config = AddConfig(chunker=chunker_config)
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44", config=add_config)
# Example: Reset to default
add_config = AddConfig()
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf", add_config)
naval_chat_bot.add("web_page", "https://nav.al/feedback", add_config)
naval_chat_bot.add("web_page", "https://nav.al/agi", add_config)
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf", config=add_config)
naval_chat_bot.add("https://nav.al/feedback", config=add_config)
naval_chat_bot.add("https://nav.al/agi", config=add_config)
naval_chat_bot.add(("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."), config=add_config)
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."), add_config)
query_config = QueryConfig() # Currently no options
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config))
# Change the number of documents.
query_config = LlmConfig(number_documents=5)
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", config=query_config))
```
### Custom prompt template
@@ -44,19 +54,22 @@ print(naval_chat_bot.query("What unique capacity does Naval argue humans possess
Here's the example of using custom prompt template with `.query`
```python
from embedchain.config import QueryConfig
from embedchain.embedchain import App
from string import Template
import wikipedia
from embedchain import App
from embedchain.config import LlmConfig
einstein_chat_bot = App()
# Embed Wikipedia page
page = wikipedia.page("Albert Einstein")
einstein_chat_bot.add("text", page.content)
einstein_chat_bot.add(page.content)
# Example: use your own custom template with `$context` and `$query`
einstein_chat_template = Template("""
einstein_chat_template = Template(
"""
You are Albert Einstein, a German-born theoretical physicist,
widely ranked among the greatest and most influential scientists of all time.
@@ -67,17 +80,19 @@ einstein_chat_template = Template("""
Keep the response brief. If you don't know the answer, just say that you don't know, don't try to make up an answer.
Human: $query
Albert Einstein:""")
query_config = QueryConfig(einstein_chat_template)
Albert Einstein:"""
)
# Example: Use the template, also add a system prompt.
llm_config = LlmConfig(template=einstein_chat_template, system_prompt="You are Albert Einstein.")
queries = [
"Where did you complete your studies?",
"Why did you win nobel prize?",
"Why did you divorce your first wife?",
"Where did you complete your studies?",
"Why did you win nobel prize?",
"Why did you divorce your first wife?",
]
for query in queries:
response = einstein_chat_bot.query(query, query_config)
print("Query: ", query)
print("Response: ", response)
response = einstein_chat_bot.query(query, config=llm_config)
print("Query: ", query)
print("Response: ", response)
# Output
# Query: Where did you complete your studies?
@@ -87,22 +102,3 @@ for query in queries:
# Query: Why did you divorce your first wife?
# Response: We divorced due to living apart for five years.
```
## Other methods
### Reset
Resets the database and deletes all embeddings. Irreversible. Requires reinitialization afterwards.
```python
app.reset()
```
### Count
Counts the number of embeddings (chunks) in the database.
```python
print(app.count())
# returns: 481
```
+70 -12
View File
@@ -2,14 +2,40 @@
title: '📋 Supported data formats'
---
Embedchain supports following data formats:
## Automatic data type detection
The add method automatically tries to detect the data_type, based on your input for the source argument. So `app.add('https://www.youtube.com/watch?v=dQw4w9WgXcQ')` is enough to embed a YouTube video.
This detection is implemented for all formats. It is based on factors such as whether it's a URL, a local file, the source data type, etc.
### Debugging automatic detection
Set `log_level=DEBUG` (in [AppConfig](http://localhost:3000/advanced/query_configuration#appconfig)) and make sure it's working as intended.
Otherwise, you will not know when, for instance, an invalid filepath is interpreted as raw text instead.
### Forcing a data type
To omit any issues with the data type detection, you can **force** a data_type by adding it as a `add` method argument.
The examples below show you the keyword to force the respective `data_type`.
Forcing can also be used for edge cases, such as interpreting a sitemap as a web_page, for reading its raw text instead of following links.
## Remote Data Types
<Tip>
**Use local files in remote data types**
Some data_types are meant for remote content and only work with URLs.
You can pass local files by formatting the path using the `file:` [URI scheme](https://en.wikipedia.org/wiki/File_URI_scheme), e.g. `file:///info.pdf`.
</Tip>
### Youtube video
To add any youtube video to your app, use the data_type (first argument to `.add()` method) as `youtube_video`. Eg:
```python
app.add('youtube_video', 'a_valid_youtube_url_here')
app.add('a_valid_youtube_url_here', data_type='youtube_video')
```
### PDF file
@@ -17,7 +43,7 @@ app.add('youtube_video', 'a_valid_youtube_url_here')
To add any pdf file, use the data_type as `pdf_file`. Eg:
```python
app.add('pdf_file', 'a_valid_url_where_pdf_file_can_be_accessed')
app.add('a_valid_url_where_pdf_file_can_be_accessed', data_type='pdf_file')
```
Note that we do not support password protected pdfs.
@@ -27,15 +53,33 @@ Note that we do not support password protected pdfs.
To add any web page, use the data_type as `web_page`. Eg:
```python
app.add('web_page', 'a_valid_web_page_url')
app.add('a_valid_web_page_url', data_type='web_page')
```
### Sitemap
Add all web pages from an xml-sitemap. Filters non-text files. Use the data_type as `sitemap`. Eg:
```python
app.add('https://example.com/sitemap.xml', data_type='sitemap')
```
### Doc file
To add any doc/docx file, use the data_type as `docx`. Eg:
To add any doc/docx file, use the data_type as `docx`. `docx` allows remote urls and conventional file paths. Eg:
```python
app.add('docx', 'a_local_docx_file_path')
app.add('https://example.com/content/intro.docx', data_type="docx")
app.add('content/intro.docx', data_type="docx")
```
### CSV file
To add any csv file, use the data_type as `csv`. `csv` allows remote urls and conventional file paths. Headers are included for each line, so if you have an `age` column, `18` will be added as `age: 18`. Eg:
```python
app.add('https://example.com/content/sheet.csv', data_type="csv")
app.add('content/sheet.csv', data_type="csv")
```
### Code documentation website loader
@@ -43,15 +87,29 @@ app.add('docx', 'a_local_docx_file_path')
To add any code documentation website as a loader, use the data_type as `docs_site`. Eg:
```python
app.add("docs_site", "https://docs.embedchain.ai/")
app.add("https://docs.embedchain.ai/", data_type="docs_site")
```
### Notion
To use notion you must install the extra dependencies with `pip install embedchain[notion]`.
To load a notion page, use the data_type as `notion`. Since it is hard to automatically detect, forcing this is advised.
The next argument must **end** with the `notion page id`. The id is a 32-character string. Eg:
```python
app.add("cfbc134ca6464fc980d0391613959196", "notion")
app.add("my-page-cfbc134ca6464fc980d0391613959196", "notion")
app.add("https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196", "notion")
```
## Local Data Types
### Text
To supply your own text, use the data_type as `text` and enter a string. The text is not processed, this can be very versatile. Eg:
```python
app.add_local('text', 'Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.')
app.add('Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.', data_type='text')
```
Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit.
@@ -61,7 +119,7 @@ Note: This is not used in the examples because in most cases you will supply a w
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```python
app.add_local('qna_pair', ("Question", "Answer"))
app.add(("Question", "Answer"), data_type="qna_pair")
```
## Reusing a vector database
@@ -74,8 +132,8 @@ Create a local index:
from embedchain import App
naval_chat_bot = App()
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
```
You can reuse the local index with the same code, but without adding new documents:
@@ -87,6 +145,6 @@ naval_chat_bot = App()
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
```
### More formats (coming soon!)
## More formats (coming soon!)
- If you want to add any other format, please create an [issue](https://github.com/embedchain/embedchain/issues) and we will add it to the list of supported formats.
+75
View File
@@ -0,0 +1,75 @@
---
title: '🤝 Interface types'
---
## Interface Types
The embedchain app exposes the following methods.
### Query Interface
- This interface is like a question answering bot. It takes a question and gets the answer. It does not maintain context about the previous chats.❓
- To use this, call `.query()` function to get the answer for any query.
```python
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
### Chat Interface
- This interface is a chat interface that remembers previous conversations. Right now it remembers 5 conversations by default. 💬
- To use this, call `.chat` function to get the answer for any query.
```python
print(naval_chat_bot.chat("How to be happy in life?"))
# answer: The most important trick to being happy is to realize happiness is a skill you develop and a choice you make. You choose to be happy, and then you work at it. It's just like building muscles or succeeding at your job. It's about recognizing the abundance and gifts around you at all times.
print(naval_chat_bot.chat("who is naval ravikant?"))
# answer: Naval Ravikant is an Indian-American entrepreneur and investor.
print(naval_chat_bot.chat("what did the author say about happiness?"))
# answer: The author, Naval Ravikant, believes that happiness is a choice you make and a skill you develop. He compares the mind to the body, stating that just as the body can be molded and changed, so can the mind. He emphasizes the importance of being present in the moment and not getting caught up in regrets of the past or worries about the future. By being present and grateful for where you are, you can experience true happiness.
```
#### Dry Run
Dry Run is an option in the `query` and `chat` methods that allows the user to not send their constructed prompt to the LLM, to save money. It's used for [testing](/advanced/testing#dry-run).
### Stream Response
- You can add config to your query method to stream responses like ChatGPT does. You would require a downstream handler to render the chunk in your desirable format. Supports both OpenAI model and OpenSourceApp. 📊
- To use this, instantiate a `QueryConfig` or `ChatConfig` object with `stream=True`. Then pass it to the `.chat()` or `.query()` method. The following example iterates through the chunks and prints them as they appear.
```python
app = App()
query_config = QueryConfig(stream = True)
resp = app.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config)
for chunk in resp:
print(chunk, end="", flush=True)
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
### Other Methods
#### Reset
Resets the database and deletes all embeddings. Irreversible. Requires reinitialization afterwards.
```python
app.reset()
```
#### Count
Counts the number of embeddings (chunks) in the database.
```python
print(app.count())
# returns: 481
```
+26 -7
View File
@@ -2,13 +2,15 @@
title: '🔍 Query configurations'
---
## InitConfig
## AppConfig
| option | description | type | default |
|-----------|-----------------------|---------------------------------|------------------------|
| log_level | log level | string | WARNING |
| ef | embedding function | chromadb.utils.embedding_functions | \{text-embedding-ada-002\} |
| embedding_fn| embedding function | chromadb.utils.embedding_functions | \{text-embedding-ada-002\} |
| db | vector database (experimental) | BaseVectorDB | ChromaDB |
| collection_name | initial collection name for the database | string | embedchain_store |
| collect_metrics | collect anonymous telemetry data to improve embedchain | boolean | true |
## AddConfig
@@ -18,6 +20,14 @@ title: '🔍 Query configurations'
|chunker|chunker config|ChunkerConfig|Default values for chunker depends on the `data_type`. Please refer [ChunkerConfig](#chunker-config)|
|loader|loader config|LoaderConfig|None|
Yes, you are passing `ChunkerConfig` to `AddConfig`, like so:
```python
chunker_config = ChunkerConfig(chunk_size=100)
add_config = AddConfig(chunker=chunker_config)
app.add("lorem ipsum", config=add_config)
```
### ChunkerConfig
|option|description|type|default|
@@ -36,18 +46,27 @@ Default values of chunker config parameters for different `data_type`:
|web_page|500|0|len|
|pdf_file|1000|0|len|
|youtube_video|2000|0|len|
|docs_site|500|50|len|
|notion|300|0|len|
### LoaderConfig
_coming soon_
## QueryConfig
## LlmConfig
|option|description|type|default|
|---|---|---|---|
|template|custom template for prompt|Template|Template("Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. \$context Query: \$query Helpful Answer:")|
|history|include conversation history from your client or database|any (recommendation: list[str])|None
|stream|control if response is streamed back to the user|bool|False|
|number_documents|Absolute number of documents to pull from the database as context.|int|1
|template|custom template for prompt. If history is used with query, $history has to be included as well.|Template|Template("Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. \$context Query: \$query Helpful Answer:")|
|model|name of the model used.|string|depends on app type|
|temperature|Controls the randomness of the model's output. Higher values (closer to 1) make output more random, lower values make it more deterministic.|float|0|
|max_tokens|Controls how many tokens are used. Exact implementation (whether it counts prompt and/or response) depends on the model.|int|1000|
|top_p|Controls the diversity of words. Higher values (closer to 1) make word selection more diverse, lower values make words less diverse.|float|1|
|history|include conversation history from your client or database.|any (recommendation: list[str])|None|
|stream|control if response is streamed back to the user.|bool|False|
|deployment_name|t.b.a.|str|None|
|system_prompt|System prompt string. Unused if none.|str|None|
## ChatConfig
@@ -55,4 +74,4 @@ All options for query and...
_coming soon_
History is handled automatically, the config option is not supported.
`history` is not supported, as that is handled is handled automatically, the config option is not supported.
+60 -2
View File
@@ -8,40 +8,69 @@ Embedchain community has been super active in creating demos on top of Embedchai
### Open Source
- [My GSoC23 bot- Streamlit chat](https://github.com/lucifertrj/EmbedChain_GSoC23_BOT) by Tarun Jain
- [Discord Bot for LLM chat](https://github.com/Reidond/discord_bots_playground/tree/c8b0c36541e4b393782ee506804c4b6962426dd6/python/chat-channel-bot) by Reidond
- [EmbedChain-Streamlit-Docker App](https://github.com/amjadraza/embedchain-streamlit-app) by amjadraza
- [Harry Potter Philosphers Stone Bot](https://github.com/vinayak-kempawad/Harry_Potter_Philosphers_Stone_Bot/) by Vinayak Kempawad, ([linkedin post](https://www.linkedin.com/feed/update/urn:li:activity:7080907532155686912/))
- [Harry Potter Philosphers Stone Bot](https://github.com/vinayak-kempawad/Harry_Potter_Philosphers_Stone_Bot/) by Vinayak Kempawad, ([LinkedIn post](https://www.linkedin.com/feed/update/urn:li:activity:7080907532155686912/))
- [LLM bot trained on own messages](https://github.com/Harin329/harinBot) by Hao Wu
### Closed Source
- [Taobot.io](https://taobot.io) - chatbot & knowledgebase hybrid by [cachho](https://github.com/cachho)
- [Create Instant ChatBot 🤖 using embedchain](https://databutton.com/v/h3e680h9) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1674704745154641920/))
- [JOBO 🤖 — The AI-driven sidekick to craft your resume](https://try-jobo.com/) by Enrico Willemse, ([LinkedIn Post](https://www.linkedin.com/posts/enrico-willemse_jobai-gptfun-embedchain-activity-7090340080879374336-ueLB/))
- [Explore Your Knowledge Base: Interactive chats over various forms of documents](https://chatdocs.dkedar.com/) by Kedar Dabhadkar, ([LinkedIn Post](https://www.linkedin.com/posts/dkedar7_machinelearning-llmops-activity-7092524836639424513-2O3L/))
- [Chatbot trained on 1000+ videos of Ester hicks the co-author behind the famous book Secret](https://ask-abraham.thoughtseed.repl.co) by Mohan Kumar
## Templates
### Replit
- [Embedchain Chat Bot](https://replit.com/@taranjeet1/Embedchain-Chat-Bot) by taranjeetio
- [Embedchain Memory Chat Bot Template](https://replit.com/@taranjeetio/Embedchain-Memory-Chat-Bot-Template) by taranjeetio
- [Chatbot app to demonstrate question-answering using retrieved information](https://replit.com/@AllisonMorrell/EmbedChainlitPublic) by Allison Morrell, ([LinkedIn Post](https://www.linkedin.com/posts/allison-morrell-2889275a_retrievalbot-screenshots-activity-7080339991754649600-wihZ/))
## Posts
### Blogs
- [Customer Service LINE Bot](https://www.evanlin.com/langchain-embedchain/)
- [Customer Service LINE Bot](https://www.evanlin.com/langchain-embedchain/) by Evan Lin
- [Chatbot in Under 5 mins using Embedchain](https://medium.com/@ayush.wattal/chatbot-in-under-5-mins-using-embedchain-a4f161fcf9c5) by Ayush Wattal
- [Understanding what the LLM framework embedchain does](https://zenn.dev/hijikix/articles/4bc8d60156a436) by Daisuke Hashimoto
- [In bed with GPT and Node.js](https://dev.to/worldlinetech/in-bed-with-gpt-and-nodejs-4kh2) by Raphaël Semeteys, ([LinkedIn Post](https://www.linkedin.com/posts/raphaelsemeteys_in-bed-with-gpt-and-nodejs-activity-7088113552326029313-nn87/))
- [Using Embedchain — A powerful LangChain Python wrapper to build Chat Bots even faster!⚡](https://medium.com/@avra42/using-embedchain-a-powerful-langchain-python-wrapper-to-build-chat-bots-even-faster-35c12994a360) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1686767751560310784/))
- [What is the Embedchain library?](https://jahaniwww.com/%da%a9%d8%aa%d8%a7%d8%a8%d8%ae%d8%a7%d9%86%d9%87-embedchain/) by Ali Jahani, ([LinkedIn Post](https://www.linkedin.com/posts/ajahani_aepaetaeqaexaggahyaeu-aetaexaesabraeaaeqaepaeu-activity-7097605202135904256-ppU-/))
- [LangChain is Nice, But Have You Tried EmbedChain ?](https://medium.com/thoughts-on-machine-learning/langchain-is-nice-but-have-you-tried-embedchain-215a34421cde) by FS Ndzomga, ([Tweet](https://twitter.com/ndzfs/status/1695583640372035951/))
- [Simplest Method to Build a Custom Chatbot with GPT-3.5 (via Embedchain)](https://www.ainewsletter.today/p/simplest-method-to-build-a-custom) by Arjun, ([Tweet](https://twitter.com/aiguy_arjun/status/1696393808467091758/))
### LinkedIn
- [What is embedchain](https://www.linkedin.com/posts/activity-7079393104423698432-wRyi/) by Rithesh Sreenivasan
- [Building a chatbot with EmbedChain](https://www.linkedin.com/posts/activity-7078434598984060928-Zdso/) by Lior Sinclair
- [Making chatbot without vs with embedchain](https://www.linkedin.com/posts/kalyanksnlp_llms-chatbots-langchain-activity-7077453416221863936-7N1L/) by Kalyan KS
- [EmbedChain - very intuitive, first you index your data and then query!](https://www.linkedin.com/posts/shubhamsaboo_embedchain-a-framework-to-easily-create-activity-7079535460699557888-ad1X/) by Shubham Saboo
- [EmbedChain - Harnessing power of LLM](https://www.linkedin.com/posts/uditsaini_chatbotrevolution-llmpoweredbots-embedchainframework-activity-7077520356827181056-FjTK/) by Udit S.
- [AI assistant for ABBYY Vantage](https://www.linkedin.com/posts/maximevermeir_llm-github-abbyy-activity-7081658972071424000-fXfZ/) by Maxime V.
- [About embedchain](https://www.linkedin.com/feed/update/urn:li:activity:7080984218914189312/) by Morris Lee
- [How to use Embedchain](https://www.linkedin.com/posts/nehaabansal_github-embedchainembedchain-framework-activity-7085830340136595456-kbW5/) by Neha Bansal
- [Youtube/Webpage summary for Energy Study](https://www.linkedin.com/posts/bar%C4%B1%C5%9F-sanl%C4%B1-34b82715_enerji-python-activity-7082735341563977730-Js0U/) by Barış Sanlı, ([Tweet](https://twitter.com/barissanli/status/1676968784979193857/))
- [Demo: How to use Embedchain? (Contains Collab Notebook link)](https://www.linkedin.com/posts/liorsinclair_embedchain-is-getting-a-lot-of-traction-because-activity-7103044695995424768-RckT/) by Lior Sinclair
### Twitter
- [What is embedchain](https://twitter.com/AlphaSignalAI/status/1672668574450847745) by Lior
- [Building a chatbot with Embedchain](https://twitter.com/Saboo_Shubham_/status/1673537044419686401) by Shubham Saboo
- [Chatbot docker image behind an API with yaml configs with Embedchain](https://twitter.com/tricalt/status/1678411430192730113/) by Vasilije
- [Build AI powered PDF chatbot with just five lines of Python code with Embedchain!](https://twitter.com/Saboo_Shubham_/status/1676627104866156544/) by Shubham Saboo
- [Chatbot against a youtube video using embedchain](https://twitter.com/smaameri/status/1675201443043704834/) by Sami Maameri
- [Highlights of EmbedChain](https://twitter.com/carl_AIwarts/status/1673542204328120321/) by carl_AIwarts
- [Build Llama-2 chatbot in less than 5 minutes](https://twitter.com/Saboo_Shubham_/status/1682168956918833152/) by Shubham Saboo
- [All cool features of embedchain](https://twitter.com/DhravyaShah/status/1683497882438217728/) by Dhravya Shah, ([LinkedIn Post](https://www.linkedin.com/posts/dhravyashah_what-if-i-tell-you-that-you-can-make-an-ai-activity-7089459599287726080-ZIYm/))
- [Read paid Medium articles for Free using embedchain](https://twitter.com/kumarkaushal_/status/1688952961622585344) by Kaushal Kumar
## Videos
- [Embedchain in one shot](https://www.youtube.com/watch?v=vIhDh7H73Ww&t=82s) by AI with Tarun
- [embedChain Create LLM powered bots over any dataset Python Demo Tesla Neurallink Chatbot Example](https://www.youtube.com/watch?v=bJqAn22a6Gc) by Rithesh Sreenivasan
- [Embedchain - NEW 🔥 Langchain BABY to build LLM Bots](https://www.youtube.com/watch?v=qj_GNQ06I8o) by 1littlecoder
- [EmbedChain -- NEW!: Build LLM-Powered Bots with Any Dataset](https://www.youtube.com/watch?v=XmaBezzGHu4) by DataInsightEdge
@@ -49,9 +78,38 @@ Embedchain community has been super active in creating demos on top of Embedchai
- [How To Create A Custom Knowledge AI Powered Bot | Install + How To Use](https://www.youtube.com/watch?v=VfCrIiAst-c) by The Ai Solopreneur
- [Build Custom Chatbot in 6 min with this Framework [Beginner Friendly]](https://www.youtube.com/watch?v=-8HxOpaFySM) by Maya Akim
- [embedchain-streamlit-app](https://www.youtube.com/watch?v=3-9GVd-3v74) by Amjad Raza
- [🤖CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN - a LangChain wrapper, in few lines of code !](https://www.youtube.com/watch?v=Mp7zJe4TIdM) by Avra
- [Building resource-driven LLM-powered bots with Embedchain](https://www.youtube.com/watch?v=IVfcAgxTO4I) by BugBytes
- [embedchain-streamlit-demo](https://www.youtube.com/watch?v=yJAWB13FhYQ) by Amjad Raza
- [Embedchain - create your own AI chatbots using open source models](https://www.youtube.com/shorts/O3rJWKwSrWE) by Dhravya Shah
- [AI ChatBot in 5 lines Python Code](https://www.youtube.com/watch?v=zjWvLJLksv8) by Data Engineering
- [Interview with Karl Marx](https://www.youtube.com/watch?v=5Y4Tscwj1xk) by Alexander Ray Williams
- [Vlog where we try to build a bot based on our content on the internet](https://www.youtube.com/watch?v=I2w8CWM3bx4) by DV, ([Tweet](https://twitter.com/dvcoolster/status/1688387017544261632))
- [CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN|STREAMLIT with MEMORY |All OPENSOURCE](https://www.youtube.com/watch?v=TqQIHWoWTDQ&pp=ygUKZW1iZWRjaGFpbg%3D%3D) by DataInsightEdge
- [Build POWERFUL LLM Bots EASILY with Your Own Data - Embedchain - Langchain 2.0? (Tutorial)](https://www.youtube.com/watch?v=jE24Y_GasE8) by WorldofAI, ([Tweet](https://twitter.com/intheworldofai/status/1696229166922780737))
- [Embedchain: An AI knowledge base assistant for customizing enterprise private data, which can be connected to discord, whatsapp, slack, tele and other terminals (with gradio to build a request interface) in Chinese](https://www.youtube.com/watch?v=5RZzCJRk-d0) by AIGC LINK
- [Embedchain Introduction](https://www.youtube.com/watch?v=Jet9zAqyggI) by Fahd Mirza
## Mentions
### Github repos
- [Awesome-LLM](https://github.com/Hannibal046/Awesome-LLM)
- [awesome-chatgpt-api](https://github.com/reorx/awesome-chatgpt-api)
- [awesome-langchain](https://github.com/kyrolabs/awesome-langchain)
- [Awesome-Prompt-Engineering](https://github.com/promptslab/Awesome-Prompt-Engineering)
- [awesome-chatgpt](https://github.com/eon01/awesome-chatgpt)
- [Awesome-LLMOps](https://github.com/tensorchord/Awesome-LLMOps)
- [awesome-generative-ai](https://github.com/filipecalegario/awesome-generative-ai)
- [awesome-gpt](https://github.com/formulahendry/awesome-gpt)
- [awesome-ChatGPT-repositories](https://github.com/taishi-i/awesome-ChatGPT-repositories)
- [awesome-gpt-prompt-engineering](https://github.com/snwfdhmp/awesome-gpt-prompt-engineering)
- [awesome-chatgpt](https://github.com/awesome-chatgpt/awesome-chatgpt)
- [awesome-llm-and-aigc](https://github.com/sjinzh/awesome-llm-and-aigc)
- [awesome-compbio-chatgpt](https://github.com/csbl-br/awesome-compbio-chatgpt)
- [Awesome-LLM4Tool](https://github.com/OpenGVLab/Awesome-LLM4Tool)
## Meetups
- [Dash and ChatGPT: Future of AI-enabled apps 30/08/23](https://go.plotly.com/dash-chatgpt)
- [Pie & AI: Bangalore - Build end-to-end LLM app using Embedchain 01/09/23](https://www.eventbrite.com/e/pie-ai-bangalore-build-end-to-end-llm-app-using-embedchain-tickets-698045722547)
+6 -2
View File
@@ -2,14 +2,18 @@
title: '🧪 Testing'
---
## Methods for testing
### Dry Run
Before you consume valueable tokens, you should make sure that the embedding you have done works and that it's receiving the correct document from the database.
For this you can use the `dry_run` method.
For this you can use the `dry_run` option in your `query` or `chat` method.
Following the example above, add this to your script:
```python
print(naval_chat_bot.dry_run('Can you tell me who Naval Ravikant is?'))
print(naval_chat_bot.query('Can you tell me who Naval Ravikant is?', dry_run=True))
'''
Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
+34
View File
@@ -0,0 +1,34 @@
---
title: '💾 Vector Database'
---
We support `Chroma` and `Elasticsearch` as two vector database.
`Chroma` is used as a default database.
### Elasticsearch
In order to use `Elasticsearch` as vector database we need to use App type `CustomApp`.
```python
import os
from embedchain import CustomApp
from embedchain.config import CustomAppConfig, ElasticsearchDBConfig
from embedchain.models import Providers, EmbeddingFunctions, VectorDatabases
os.environ["OPENAI_API_KEY"] = 'OPENAI_API_KEY'
es_config = ElasticsearchDBConfig(
# elasticsearch url or list of nodes url with different hosts and ports.
es_url='http://localhost:9200',
# pass named parameters supported by Python Elasticsearch client
ca_certs="/path/to/http_ca.crt",
basic_auth=("username", "password")
)
config = CustomAppConfig(
embedding_fn=EmbeddingFunctions.OPENAI,
provider=Providers.OPENAI,
db_type=VectorDatabases.ELASTICSEARCH,
es_config=es_config,
)
es_app = CustomApp(config)
```
- Set `db_type=VectorDatabases.ELASTICSEARCH` and `es_config=ElasticsearchDBConfig(es_url='')` in `CustomAppConfig`.
- `ElasticsearchDBConfig` accepts `es_url` as elasticsearch url or as list of nodes url with different hosts and ports. Additionally we can pass named parameters supported by Python Elasticsearch client.
+91
View File
@@ -0,0 +1,91 @@
---
title: '🌍 API Server'
---
The API Server based on Flask integrates the `embedchain` package, offering endpoints to add, query, and chat to engage in conversations with a chatbot using JSON requests.
### 🐳 Docker Setup
- Open variables.env, and edit it to add your 🔑 `OPENAI_API_KEY`.
- To setup your api server using docker, run the following command inside this folder using your terminal.
```bash
docker-compose up --build
```
📝 Note: The build command might take a while to install all the packages depending on your system resources.
### 🚀 Usage Instructions
- Your api server is running on [http://localhost:5000/](http://localhost:5000/)
- To use the api server, make an api call to the endpoints `/add`, `/query` and `/chat` using the json formats discussed below.
- To add data sources to the bot (/add):
```json
// Request
{
"data_type": "your_data_type_here",
"url_or_text": "your_url_or_text_here"
}
// Response
{
"data": "Added data_type: url_or_text"
}
```
- To ask queries from the bot (/query):
```json
// Request
{
"question": "your_question_here"
}
// Response
{
"data": "your_answer_here"
}
```
- To chat with the bot (/chat):
```json
// Request
{
"question": "your_question_here"
}
// Response
{
"data": "your_answer_here"
}
```
### 📡 Curl Call Formats
- To add data sources to the bot (/add):
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"data_type": "your_data_type_here",
"url_or_text": "your_url_or_text_here"
}' \
http://localhost:5000/add
```
- To ask queries from the bot (/query):
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"question": "your_question_here"
}' \
http://localhost:5000/query
```
- To chat with the bot (/chat):
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"question": "your_question_here"
}' \
http://localhost:5000/chat
```
🎉 Happy Chatting! 🎉
+58
View File
@@ -0,0 +1,58 @@
---
title: '🤖 Discord Bot'
---
### 🔑 Keys Setup
- Set your `OPENAI_API_KEY` in your variables.env file.
- Go to [https://discord.com/developers/applications/](https://discord.com/developers/applications/) and click on `New Application`.
- Enter the name for your bot, accept the terms and click on `Create`. On the resulting page, enter the details of your bot as you like.
- On the left sidebar, click on `Bot`. Under the heading `Privileged Gateway Intents`, toggle all 3 options to ON position. Save your changes.
- Now click on `Reset Token` and copy the token value. Set it as `DISCORD_BOT_TOKEN` in .env file.
- On the left sidebar, click on `OAuth2` and go to `General`.
- Set `Authorization Method` to `In-app Authorization`. Under `Scopes` select `bot`.
- Under `Bot Permissions` allow the following and then click on `Save Changes`.
```text
Send Messages (under Text Permissions)
```
- Now under `OAuth2` and go to `URL Generator`. Under `Scopes` select `bot`.
- Under `Bot Permissions` set the same permissions as above.
- Now scroll down and copy the `Generated URL`. Paste it in a browser window and select the Server where you want to add the bot.
- Click on `Continue` and authorize the bot.
- 🎉 The bot has been successfully added to your server. But it's still offline.
### Take the bot online
1. Install embedchain python package:
```bash
pip install "embedchain[discord]"
```
2. Launch your Discord bot:
```bash
python -m embedchain.bots.discord
```
If you prefer to see the question and not only the answer, run it with
```bash
python -m embedchain.bots.discord --include-question
```
### 🚀 Usage Instructions
- Go to the server where you have added your bot.
- You can add data sources to the bot using the slash command:
```text
/add <data_type> <url_or_text>
```
- You can ask your queries from the bot using the slash command:
```text
/query <question>
```
📝 Note: To use the bot privately, you can message the bot directly by right clicking the bot and selecting `Message`.
🎉 Happy Chatting! 🎉
+22
View File
@@ -0,0 +1,22 @@
---
title: '🌐 Full Stack'
---
### 🐳 Docker Setup
- To setup full stack app using docker, run the following command inside this folder using your terminal.
```bash
docker-compose up --build
```
📝 Note: The build command might take a while to install all the packages depending on your system resources.
### 🚀 Usage Instructions
- Go to [http://localhost:3000/](http://localhost:3000/) in your browser to view the dashboard.
- Add your `OpenAI API key` 🔑 in the Settings.
- Create a new bot and you'll be navigated to its page.
- Here you can add your data sources and then chat with the bot.
🎉 Happy Chatting! 🎉
+59
View File
@@ -0,0 +1,59 @@
---
title: '🔮 Poe Bot'
---
### 🚀 Getting started
1. Install embedchain python package:
```bash
pip install "embedchain[poe]"
```
2. Create a free account on [Poe](https://www.poe.com?utm_source=embedchain).
3. Click "Create Bot" button on top left.
4. Give it a handle and an optional description.
5. Select `Use API`.
6. Under `API URL` enter your server or ngrok address. You can use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible.
7. Copy your api key and paste it in `.env` as `POE_API_KEY`.
8. You will need to set `OPENAI_API_KEY` for generating embeddings and using LLM. Copy your OpenAI API key from [here](https://platform.openai.com/account/api-keys) and paste it in `.env` as `OPENAI_API_KEY`.
9. Now create your bot using the following code snippet.
```bash
# make sure that you have set OPENAI_API_KEY and POE_API_KEY in .env file
from embedchain.bots import PoeBot
poe_bot = PoeBot()
# add as many data sources as you want
poe_bot.add("https://en.wikipedia.org/wiki/Adam_D%27Angelo")
poe_bot.add("https://www.youtube.com/watch?v=pJQVAqmKua8")
# start the bot
# this start the poe bot server on port 8080 by default
poe_bot.start()
```
10. You can paste the above in a file called `your_script.py` and then simply do
```bash
python your_script.py
```
Now your bot will start running at port `8080` by default.
11. You can refer the [Supported Data formats](https://docs.embedchain.ai/advanced/data_types) section to refer the supported data types in embedchain.
12. Click `Run check` to make sure your machine can be reached.
13. Make sure your bot is private if that's what you want.
14. Click `Create bot` at the bottom to finally create the bot
15. Now your bot is created.
### 💬 How to use
- To ask the bot questions, just type your query in the Poe interface:
```text
<your-question-here>
```
- If you wish to add more data source to the bot, simply update your script and add as many `.add` as you like. You need to restart the server.
+37
View File
@@ -0,0 +1,37 @@
---
title: '💼 Slack Bot'
---
### 🖼️ Setup
1. Create a workspace on Slack if you don't have one already by clicking [here](https://slack.com/intl/en-in/).
2. Create a new App on your Slack account by going [here](https://api.slack.com/apps).
3. Select `From Scratch`, then enter the Bot Name and select your workspace.
4. On the left Sidebar, go to `OAuth and Permissions` and add the following scopes under `Bot Token Scopes`:
```text
app_mentions:read
channels:history
channels:read
chat:write
```
5. Now select the option `Install to Workspace` and after it's done, copy the `Bot User OAuth Token` and set it in your secrets as `SLACK_BOT_TOKEN`.
6. Run your bot now with `python3 -m embedchain.bots.slack`
7. Expose your bot to the internet. Default port is `5000`, which can be changed by adding `port --8080` to the startup command. You can use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible.
8. On the Slack API website go to `Event Subscriptions` on the left Sidebar and turn on `Enable Events`.
9. In `Request URL`, enter your server or ngrok address.
10. After it gets verified, click on `Subscribe to bot events`, add `message.channels` Bot User Event and click on `Save Changes`.
11. Now go to your workspace, right click on the bot name in the sidebar, click `view app details`, then `add this app to a channel`.
### 🚀 Usage Instructions
- Go to the channel where you have added your bot.
- To add data sources to the bot, use the command:
```text
add <data_type> <url_or_text>
```
- To ask queries from the bot, use the command:
```text
query <question>
```
🎉 Happy Chatting! 🎉
+26
View File
@@ -0,0 +1,26 @@
---
title: '📱 Telegram Bot'
---
### 🖼️ Template Setup
- Fork [this](https://replit.com/@taranjeetio/EC-Telegram-Bot-Template?v=1#README.md) replit template.
- Set your `OPENAI_API_KEY` in Secrets.
- Open the Telegram app and search for the `BotFather` user.
- Start a chat with BotFather and use the `/newbot` command to create a new bot.
- Follow the instructions to choose a name and username for your bot.
- Once the bot is created, BotFather will provide you with a unique token for your bot.
- Set this token as `TELEGRAM_BOT_TOKEN` in Secrets.
- Click on `Run` in the replit container and a URL will get generated for your bot.
- Now set your webhook by running the following link in your browser:
```url
https://api.telegram.org/bot<Your_Telegram_Bot_Token>/setWebhook?url=<Replit_Generated_URL>
```
- When you get a successful response in your browser, your bot is ready to be used.
### 🚀 Usage Instructions
- Open your bot by searching for it using the bot name or bot username.
- Click on `Start` or type `/start` and follow the on screen instructions.
🎉 Happy Chatting! 🎉
+46
View File
@@ -0,0 +1,46 @@
---
title: '💬 WhatsApp Bot'
---
### 🚀 Getting started
1. Install embedchain python package:
```bash
pip install embedchain
```
2. Launch your WhatsApp bot:
```bash
python -m embedchain.bots.whatsapp --port 5000
```
If your bot needs to be accessible online, use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible.
3. Create a free account on [Twilio](https://www.twilio.com/try-twilio)
- Set up a WhatsApp Sandbox in your Twilio dashboard. Access it via the left sidebar: `Messaging > Try it out > Send a WhatsApp Message`.
- Follow on-screen instructions to link a phone number for chatting with your bot
- Copy your bot's public URL, add /chat at the end, and paste it in Twilio's WhatsApp Sandbox settings under "When a message comes in". Save the settings.
- Copy your bot's public url, append `/chat` at the end and paste it under `When a message comes in` under the `Sandbox settings` for Whatsapp in Twilio. Save your settings.
### 💬 How to use
- To connect a new number or reconnect an old one in the Sandbox, follow Twilio's instructions.
- To include data sources, use this command:
```text
add <url_or_text>
```
- To ask the bot questions, just type your query:
```text
<your-question-here>
```
### Example
Here is an example of Elon Musk WhatsApp Bot that we created:
<img src="/images/whatsapp.jpg"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

+8 -8
View File
@@ -7,7 +7,7 @@ description: '📝 Embedchain is a framework to easily create LLM powered bots o
Embedchain abstracts the entire process of loading a dataset, chunking it, creating embeddings, and storing it in a vector database.
You can add a single or multiple datasets using the .add and .add_local functions. Then, simply use the .query function to find answers from the added datasets.
You can add a single or multiple datasets using the `.add` method. Then, simply use the `.query` method to find answers from the added datasets.
If you want to create a Naval Ravikant bot with a YouTube video, a book in PDF format, two blog posts, and a question and answer pair, all you need to do is add the respective links. Embedchain will take care of the rest, creating a bot for you.
@@ -16,13 +16,13 @@ from embedchain import App
naval_chat_bot = App()
# Embed Online Resources
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("web_page", "https://nav.al/feedback")
naval_chat_bot.add("web_page", "https://nav.al/agi")
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("https://nav.al/feedback")
naval_chat_bot.add("https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
naval_chat_bot.add(("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?")
# Answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
@@ -32,7 +32,7 @@ naval_chat_bot.query("What unique capacity does Naval argue humans possess when
Creating a chat bot over any dataset involves the following steps:
1. Load the data
1. Detect the data type and load the data
2. Create meaningful chunks
3. Create embeddings for each chunk
4. Store the chunks in a vector database
@@ -53,4 +53,4 @@ The process of loading the dataset and querying involves multiple steps, each wi
Embedchain takes care of all these nuances and provides a simple interface to create bots over any dataset.
In the first release, we make it easier for anyone to get a chatbot over any dataset up and running in less than a minute. Just create an app instance, add the datasets using the `.add()` function, and use the `.query()` function to get the relevant answers.
In the first release, we make it easier for anyone to get a chatbot over any dataset up and running in less than a minute. Just create an app instance, add the datasets using the `.add` method, and use the `.query` method to get the relevant answers.
+7 -2
View File
@@ -32,7 +32,11 @@
},
{
"group": "Advanced",
"pages": ["advanced/app_types", "advanced/data_types", "advanced/query_configuration", "advanced/configuration", "advanced/testing", "advanced/showcase"]
"pages": ["advanced/app_types", "advanced/interface_types", "advanced/adding_data", "advanced/data_types", "advanced/query_configuration", "advanced/configuration", "advanced/testing", "advanced/vector_database", "advanced/showcase"]
},
{
"group": "Examples",
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot", "examples/slack_bot", "examples/telegram_bot", "examples/whatsapp_bot", "examples/poe_bot"]
},
{
"group": "Contribution Guidelines",
@@ -43,7 +47,8 @@
"footerSocials": {
"twitter": "https://twitter.com/embedchain",
"github": "https://github.com/embedchain/embedchain",
"linkedin": "https://www.linkedin.com/company/embedchain"
"linkedin": "https://www.linkedin.com/company/embedchain",
"website": "https://embedchain.ai"
},
"backgroundImage": "/background.png",
"isWhiteLabeled": true
+10 -4
View File
@@ -9,21 +9,27 @@ Install embedchain python package:
pip install embedchain
```
Creating a chatbot involves 3 steps:
- ⚙️ Import the App instance
- 🗃️ Add Dataset
- 💬 Query or Chat on the dataset and get answers (Interface Types)
Run your first bot in python using the following code. Make sure to set the `OPENAI_API_KEY` 🔑 environment variable in the code.
```python
import os
from embedchain Import App
from embedchain import App
os.environ["OPENAI_API_KEY"] = "xxx"
elon_musk_bot = App()
# Embed Online Resources
elon_musk_bot.add("web_page", "https://en.wikipedia.org/wiki/Elon_Musk")
elon_musk_bot.add("web_page", "https://www.tesla.com/elon-musk")
elon_musk_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
elon_musk_bot.add("https://www.tesla.com/elon-musk")
response = elon_bot.query("How many companies does Elon Musk run?")
response = elon_musk_bot.query("How many companies does Elon Musk run?")
print(response)
# Answer: 'Elon Musk runs four companies: Tesla, SpaceX, Neuralink, and The Boring Company.'
```
+8 -4
View File
@@ -2,7 +2,11 @@ import importlib.metadata
__version__ = importlib.metadata.version(__package__ or __name__)
from .embedchain import App # noqa: F401
from .embedchain import OpenSourceApp # noqa: F401
from .embedchain import PersonApp # noqa: F401
from .embedchain import PersonOpenSourceApp # noqa: F401
from embedchain.apps.App import App # noqa: F401
from embedchain.apps.CustomApp import CustomApp # noqa: F401
from embedchain.apps.Llama2App import Llama2App # noqa: F401
from embedchain.apps.OpenSourceApp import OpenSourceApp # noqa: F401
from embedchain.apps.PersonApp import (PersonApp, # noqa: F401
PersonOpenSourceApp)
from embedchain.vectordb.chroma_db import ChromaDB # noqa: F401
from embedchain.vectordb.elasticsearch_db import ElasticsearchDB # noqa: F401
+54
View File
@@ -0,0 +1,54 @@
from typing import Optional
from embedchain.config import (AppConfig, BaseEmbedderConfig, BaseLlmConfig,
ChromaDbConfig)
from embedchain.embedchain import EmbedChain
from embedchain.embedder.openai_embedder import OpenAiEmbedder
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.openai_llm import OpenAiLlm
from embedchain.vectordb.chroma_db import ChromaDB
@register_deserializable
class App(EmbedChain):
"""
The EmbedChain app in it's simplest and most straightforward form.
An opinionated choice of LLM, vector database and embedding model.
Methods:
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
"""
def __init__(
self,
config: AppConfig = None,
llm_config: BaseLlmConfig = None,
chromadb_config: Optional[ChromaDbConfig] = None,
system_prompt: Optional[str] = None,
):
"""
Initialize a new `CustomApp` instance. You only have a few choices to make.
:param config: Config for the app instance.
This is the most basic configuration, that does not fall into the LLM, database or embedder category,
defaults to None
:type config: AppConfig, optional
:param llm_config: Allows you to configure the LLM, e.g. how many documents to return,
example: `from embedchain.config import LlmConfig`, defaults to None
:type llm_config: BaseLlmConfig, optional
:param chromadb_config: Allows you to configure the vector database,
example: `from embedchain.config import ChromaDbConfig`, defaults to None
:type chromadb_config: Optional[ChromaDbConfig], optional
:param system_prompt: System prompt that will be provided to the LLM as such, defaults to None
:type system_prompt: Optional[str], optional
"""
if config is None:
config = AppConfig()
llm = OpenAiLlm(config=llm_config)
embedder = OpenAiEmbedder(config=BaseEmbedderConfig(model="text-embedding-ada-002"))
database = ChromaDB(config=chromadb_config)
super().__init__(config, llm, db=database, embedder=embedder, system_prompt=system_prompt)
+83
View File
@@ -0,0 +1,83 @@
from typing import Optional
from embedchain.config import CustomAppConfig
from embedchain.embedchain import EmbedChain
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
from embedchain.vectordb.base_vector_db import BaseVectorDB
@register_deserializable
class CustomApp(EmbedChain):
"""
Embedchain's custom app allows for most flexibility.
You can craft your own mix of various LLMs, vector databases and embedding model/functions.
Methods:
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
"""
def __init__(
self,
config: Optional[CustomAppConfig] = None,
llm: BaseLlm = None,
db: BaseVectorDB = None,
embedder: BaseEmbedder = None,
system_prompt: Optional[str] = None,
):
"""
Initialize a new `CustomApp` instance. You have to choose a LLM, database and embedder.
:param config: Config for the app instance. This is the most basic configuration,
that does not fall into the LLM, database or embedder category, defaults to None
:type config: Optional[CustomAppConfig], optional
:param llm: LLM Class instance. example: `from embedchain.llm.openai_llm import OpenAiLlm`, defaults to None
:type llm: BaseLlm
:param db: The database to use for storing and retrieving embeddings,
example: `from embedchain.vectordb.chroma_db import ChromaDb`, defaults to None
:type db: BaseVectorDB
:param embedder: The embedder (embedding model and function) use to calculate embeddings.
example: `from embedchain.embedder.gpt4all_embedder import GPT4AllEmbedder`, defaults to None
:type embedder: BaseEmbedder
:param system_prompt: System prompt that will be provided to the LLM as such, defaults to None
:type system_prompt: Optional[str], optional
:raises ValueError: LLM, database or embedder has not been defined.
:raises TypeError: LLM, database or embedder is not a valid class instance.
"""
# Config is not required, it has a default
if config is None:
config = CustomAppConfig()
if llm is None:
raise ValueError("LLM must be provided for custom app. Please import from `embedchain.llm`.")
if db is None:
raise ValueError("Database must be provided for custom app. Please import from `embedchain.vectordb`.")
if embedder is None:
raise ValueError("Embedder must be provided for custom app. Please import from `embedchain.embedder`.")
if not isinstance(config, CustomAppConfig):
raise TypeError(
"Config is not a `CustomAppConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(llm, BaseLlm):
raise TypeError(
"LLM is not a `BaseLlm` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(db, BaseVectorDB):
raise TypeError(
"Database is not a `BaseVectorDB` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(embedder, BaseEmbedder):
raise TypeError(
"Embedder is not a `BaseEmbedder` instance. "
"Please make sure the type is right and that you are passing an instance."
)
super().__init__(config=config, llm=llm, db=db, embedder=embedder, system_prompt=system_prompt)
+33
View File
@@ -0,0 +1,33 @@
from typing import Optional
from embedchain.apps.CustomApp import CustomApp
from embedchain.config import CustomAppConfig
from embedchain.embedder.openai_embedder import OpenAiEmbedder
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.llama2_llm import Llama2Llm
from embedchain.vectordb.chroma_db import ChromaDB
@register_deserializable
class Llama2App(CustomApp):
"""
The EmbedChain Llama2App class.
Methods:
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
"""
def __init__(self, config: CustomAppConfig = None, system_prompt: Optional[str] = None):
"""
:param config: CustomAppConfig instance to load as configuration. Optional.
:param system_prompt: System prompt string. Optional.
"""
if config is None:
config = CustomAppConfig()
super().__init__(
config=config, llm=Llama2Llm(), db=ChromaDB(), embedder=OpenAiEmbedder(), system_prompt=system_prompt
)
+78
View File
@@ -0,0 +1,78 @@
import logging
from typing import Optional
from embedchain.config import (BaseEmbedderConfig, BaseLlmConfig,
ChromaDbConfig, OpenSourceAppConfig)
from embedchain.embedchain import EmbedChain
from embedchain.embedder.gpt4all_embedder import GPT4AllEmbedder
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.gpt4all_llm import GPT4ALLLlm
from embedchain.vectordb.chroma_db import ChromaDB
gpt4all_model = None
@register_deserializable
class OpenSourceApp(EmbedChain):
"""
The embedchain Open Source App.
Comes preconfigured with the best open source LLM, embedding model, database.
Methods:
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
"""
def __init__(
self,
config: OpenSourceAppConfig = None,
llm_config: BaseLlmConfig = None,
chromadb_config: Optional[ChromaDbConfig] = None,
system_prompt: Optional[str] = None,
):
"""
Initialize a new `CustomApp` instance.
Since it's opinionated you don't have to choose a LLM, database and embedder.
However, you can configure those.
:param config: Config for the app instance. This is the most basic configuration,
that does not fall into the LLM, database or embedder category, defaults to None
:type config: OpenSourceAppConfig, optional
:param llm_config: Allows you to configure the LLM, e.g. how many documents to return.
example: `from embedchain.config import LlmConfig`, defaults to None
:type llm_config: BaseLlmConfig, optional
:param chromadb_config: Allows you to configure the open source database,
example: `from embedchain.config import ChromaDbConfig`, defaults to None
:type chromadb_config: Optional[ChromaDbConfig], optional
:param system_prompt: System prompt that will be provided to the LLM as such.
Please don't use for the time being, as it's not supported., defaults to None
:type system_prompt: Optional[str], optional
:raises TypeError: `OpenSourceAppConfig` or `LlmConfig` invalid.
"""
logging.info("Loading open source embedding model. This may take some time...") # noqa:E501
if not config:
config = OpenSourceAppConfig()
if not isinstance(config, OpenSourceAppConfig):
raise TypeError(
"OpenSourceApp needs a OpenSourceAppConfig passed to it. "
"You can import it with `from embedchain.config import OpenSourceAppConfig`"
)
if not llm_config:
llm_config = BaseLlmConfig(model="orca-mini-3b.ggmlv3.q4_0.bin")
elif not isinstance(llm_config, BaseLlmConfig):
raise TypeError(
"The LlmConfig passed to OpenSourceApp is invalid. "
"You can import it with `from embedchain.config import LlmConfig`"
)
elif not llm_config.model:
llm_config.model = "orca-mini-3b.ggmlv3.q4_0.bin"
llm = GPT4ALLLlm(config=llm_config)
embedder = GPT4AllEmbedder(config=BaseEmbedderConfig(model="all-MiniLM-L6-v2"))
logging.error("Successfully loaded open source embedding model.")
database = ChromaDB(config=chromadb_config)
super().__init__(config, llm=llm, db=database, embedder=embedder, system_prompt=system_prompt)
+94
View File
@@ -0,0 +1,94 @@
from string import Template
from embedchain.apps.App import App
from embedchain.apps.OpenSourceApp import OpenSourceApp
from embedchain.config import BaseLlmConfig
from embedchain.config.apps.BaseAppConfig import BaseAppConfig
from embedchain.config.llm.base_llm_config import (DEFAULT_PROMPT,
DEFAULT_PROMPT_WITH_HISTORY)
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class EmbedChainPersonApp:
"""
Base class to create a person bot.
This bot behaves and speaks like a person.
:param person: name of the person, better if its a well known person.
:param config: BaseAppConfig instance to load as configuration.
"""
def __init__(self, person: str, config: BaseAppConfig = None):
"""Initialize a new person app
:param person: Name of the person that's imitated.
:type person: str
:param config: Configuration class instance, defaults to None
:type config: BaseAppConfig, optional
"""
self.person = person
self.person_prompt = f"You are {person}. Whatever you say, you will always say in {person} style." # noqa:E501
super().__init__(config)
def add_person_template_to_config(self, default_prompt: str, config: BaseLlmConfig = None):
"""
This method checks if the config object contains a prompt template
if yes it adds the person prompt to it and return the updated config
else it creates a config object with the default prompt added to the person prompt
:param default_prompt: it is the default prompt for query or chat methods
:type default_prompt: str
:param config: _description_, defaults to None
:type config: BaseLlmConfig, optional
:return: The `ChatConfig` instance to use as configuration options.
:rtype: _type_
"""
template = Template(self.person_prompt + " " + default_prompt)
if config:
if config.template:
# Add person prompt to custom user template
config.template = Template(self.person_prompt + " " + config.template.template)
else:
# If no user template is present, use person prompt with the default template
config.template = template
else:
# if no config is present at all, initialize the config with person prompt and default template
config = BaseLlmConfig(
template=template,
)
return config
@register_deserializable
class PersonApp(EmbedChainPersonApp, App):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and App
"""
def query(self, input_query, config: BaseLlmConfig = None, dry_run=False):
config = self.add_person_template_to_config(DEFAULT_PROMPT, config, where=None)
return super().query(input_query, config, dry_run, where=None)
def chat(self, input_query, config: BaseLlmConfig = None, dry_run=False, where=None):
config = self.add_person_template_to_config(DEFAULT_PROMPT_WITH_HISTORY, config)
return super().chat(input_query, config, dry_run, where)
@register_deserializable
class PersonOpenSourceApp(EmbedChainPersonApp, OpenSourceApp):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and OpenSourceApp
"""
def query(self, input_query, config: BaseLlmConfig = None, dry_run=False):
config = self.add_person_template_to_config(DEFAULT_PROMPT, config)
return super().query(input_query, config, dry_run)
def chat(self, input_query, config: BaseLlmConfig = None, dry_run=False):
config = self.add_person_template_to_config(DEFAULT_PROMPT_WITH_HISTORY, config)
return super().chat(input_query, config, dry_run)
View File
+5
View File
@@ -0,0 +1,5 @@
from embedchain.bots.poe import PoeBot # noqa: F401
from embedchain.bots.whatsapp import WhatsAppBot # noqa: F401
# TODO: fix discord import
# from embedchain.bots.discord import DiscordBot
+46
View File
@@ -0,0 +1,46 @@
from typing import Any
from embedchain import CustomApp
from embedchain.config import AddConfig, CustomAppConfig, LlmConfig
from embedchain.embedder.openai_embedder import OpenAiEmbedder
from embedchain.helper_classes.json_serializable import (
JSONSerializable, register_deserializable)
from embedchain.llm.openai_llm import OpenAiLlm
from embedchain.vectordb.chroma_db import ChromaDB
@register_deserializable
class BaseBot(JSONSerializable):
def __init__(self):
self.app = CustomApp(config=CustomAppConfig(), llm=OpenAiLlm(), db=ChromaDB(), embedder=OpenAiEmbedder())
def add(self, data: Any, config: AddConfig = None):
"""
Add data to the bot (to the vector database).
Auto-dectects type only, so some data types might not be usable.
:param data: data to embed
:type data: Any
:param config: configuration class instance, defaults to None
:type config: AddConfig, optional
"""
config = config if config else AddConfig()
self.app.add(data, config=config)
def query(self, query: str, config: LlmConfig = None) -> str:
"""
Query the bot
:param query: the user query
:type query: str
:param config: configuration class instance, defaults to None
:type config: LlmConfig, optional
:return: Answer
:rtype: str
"""
config = config
return self.app.query(query, config=config)
def start(self):
"""Start the bot's functionality."""
raise NotImplementedError("Subclasses must implement the start method.")
+120
View File
@@ -0,0 +1,120 @@
import argparse
import logging
import os
import discord
from discord import app_commands
from discord.ext import commands
from embedchain.helper_classes.json_serializable import register_deserializable
from .base import BaseBot
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)
# Invite link example
# https://discord.com/api/oauth2/authorize?client_id={DISCORD_CLIENT_ID}&permissions=2048&scope=bot
@register_deserializable
class DiscordBot(BaseBot):
def __init__(self, *args, **kwargs):
BaseBot.__init__(self, *args, **kwargs)
def add_data(self, message):
data = message.split(" ")[-1]
try:
self.add(data)
response = f"Added data from: {data}"
except Exception:
logging.exception(f"Failed to add data {data}.")
response = "Some error occurred while adding data."
return response
def ask_bot(self, message):
try:
response = self.query(message)
except Exception:
logging.exception(f"Failed to query {message}.")
response = "An error occurred. Please try again!"
return response
def start(self):
client.run(os.environ["DISCORD_BOT_TOKEN"])
# @tree decorator cannot be used in a class. A global discord_bot is used as a workaround.
@tree.command(name="question", description="ask embedchain")
async def query_command(interaction: discord.Interaction, question: str):
await interaction.response.defer()
member = client.guilds[0].get_member(client.user.id)
logging.info(f"User: {member}, Query: {question}")
try:
answer = discord_bot.ask_bot(question)
if args.include_question:
response = f"> {question}\n\n{answer}"
else:
response = answer
await interaction.followup.send(response)
except Exception as e:
await interaction.followup.send("An error occurred. Please try again!")
logging.error("Error occurred during 'query' command:", e)
@tree.command(name="add", description="add new content to the embedchain database")
async def add_command(interaction: discord.Interaction, url_or_text: str):
await interaction.response.defer()
member = client.guilds[0].get_member(client.user.id)
logging.info(f"User: {member}, Add: {url_or_text}")
try:
response = discord_bot.add_data(url_or_text)
await interaction.followup.send(response)
except Exception as e:
await interaction.followup.send("An error occurred. Please try again!")
logging.error("Error occurred during 'add' command:", e)
@tree.command(name="ping", description="Simple ping pong command")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message("Pong", ephemeral=True)
@tree.error
async def on_app_command_error(interaction: discord.Interaction, error: discord.app_commands.AppCommandError) -> None:
if isinstance(error, commands.CommandNotFound):
await interaction.followup.send("Invalid command. Please refer to the documentation for correct syntax.")
else:
logging.error("Error occurred during command execution:", error)
@client.event
async def on_ready():
# TODO: Sync in admin command, to not hit rate limits.
# This might be overkill for most users, and it would require to set a guild or user id, where sync is allowed.
await tree.sync()
logging.debug("Command tree synced")
logging.info(f"Logged in as {client.user.name}")
def start_command():
parser = argparse.ArgumentParser(description="EmbedChain DiscordBot command line interface")
parser.add_argument(
"--include-question",
help="include question in query reply, otherwise it is hidden behind the slash command.",
action="store_true",
)
global args
args = parser.parse_args()
global discord_bot
discord_bot = DiscordBot()
discord_bot.start()
if __name__ == "__main__":
start_command()
+82
View File
@@ -0,0 +1,82 @@
import argparse
import logging
import os
from typing import List, Optional
from fastapi_poe import PoeBot, run
from embedchain.helper_classes.json_serializable import register_deserializable
from .base import BaseBot
def start_command():
parser = argparse.ArgumentParser(description="EmbedChain PoeBot command line interface")
# parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind")
parser.add_argument("--port", default=8080, type=int, help="Port to bind")
parser.add_argument("--api-key", type=str, help="Poe API key")
# parser.add_argument(
# "--history-length",
# default=5,
# type=int,
# help="Set the max size of the chat history. Multiplies cost, but improves conversation awareness.",
# )
args = parser.parse_args()
# FIXME: Arguments are automatically loaded by Poebot's ArgumentParser which causes it to fail.
# the port argument here is also just for show, it actually works because poe has the same argument.
run(PoeBot(), api_key=args.api_key or os.environ.get("POE_API_KEY"))
@register_deserializable
class PoeBot(BaseBot, PoeBot):
def __init__(self):
self.history_length = 5
super().__init__()
async def get_response(self, query):
last_message = query.query[-1].content
try:
history = (
[f"{m.role}: {m.content}" for m in query.query[-(self.history_length + 1) : -1]]
if len(query.query) > 0
else None
)
except Exception as e:
logging.error(f"Error when processing the chat history. Message is being sent without history. Error: {e}")
answer = self.handle_message(last_message, history)
yield self.text_event(answer)
def handle_message(self, message, history: Optional[List[str]] = None):
if message.startswith("/add "):
response = self.add_data(message)
else:
response = self.ask_bot(message, history)
return response
# def add_data(self, message):
# data = message.split(" ")[-1]
# try:
# self.add(data)
# response = f"Added data from: {data}"
# except Exception:
# logging.exception(f"Failed to add data {data}.")
# response = "Some error occurred while adding data."
# return response
def ask_bot(self, message, history: List[str]):
try:
self.app.llm.set_history(history=history)
response = self.query(message)
except Exception:
logging.exception(f"Failed to query {message}.")
response = "An error occurred. Please try again!"
return response
def start(self):
start_command()
if __name__ == "__main__":
start_command()
+98
View File
@@ -0,0 +1,98 @@
import argparse
import logging
import os
import signal
import sys
from embedchain import App
from embedchain.helper_classes.json_serializable import register_deserializable
from .base import BaseBot
try:
from flask import Flask, request
from slack_sdk import WebClient
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for Slack are not installed." 'Please install with `pip install "embedchain[slack]"`'
) from None
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
@register_deserializable
class SlackBot(BaseBot):
def __init__(self):
self.client = WebClient(token=SLACK_BOT_TOKEN)
self.chat_bot = App()
self.recent_message = {"ts": 0, "channel": ""}
super().__init__()
def handle_message(self, event_data):
message = event_data.get("event")
if message and "text" in message and message.get("subtype") != "bot_message":
text: str = message["text"]
if float(message.get("ts")) > float(self.recent_message["ts"]):
self.recent_message["ts"] = message["ts"]
self.recent_message["channel"] = message["channel"]
if text.startswith("query"):
_, question = text.split(" ", 1)
try:
response = self.chat_bot.chat(question)
self.send_slack_message(message["channel"], response)
logging.info("Query answered successfully!")
except Exception as e:
self.send_slack_message(message["channel"], "An error occurred. Please try again!")
logging.error("Error occurred during 'query' command:", e)
elif text.startswith("add"):
_, data_type, url_or_text = text.split(" ", 2)
if url_or_text.startswith("<") and url_or_text.endswith(">"):
url_or_text = url_or_text[1:-1]
try:
self.chat_bot.add(url_or_text, data_type)
self.send_slack_message(message["channel"], f"Added {data_type} : {url_or_text}")
except ValueError as e:
self.send_slack_message(message["channel"], f"Error: {str(e)}")
logging.error("Error occurred during 'add' command:", e)
except Exception as e:
self.send_slack_message(message["channel"], f"Failed to add {data_type} : {url_or_text}")
logging.error("Error occurred during 'add' command:", e)
def send_slack_message(self, channel, message):
response = self.client.chat_postMessage(channel=channel, text=message)
return response
def start(self, host="0.0.0.0", port=5000, debug=True):
app = Flask(__name__)
def signal_handler(sig, frame):
logging.info("\nGracefully shutting down the SlackBot...")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
@app.route("/", methods=["POST"])
def chat():
# Check if the request is a verification request
if request.json.get("challenge"):
return str(request.json.get("challenge"))
response = self.handle_message(request.json)
return str(response)
app.run(host=host, port=port, debug=debug)
def start_command():
parser = argparse.ArgumentParser(description="EmbedChain SlackBot command line interface")
parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind")
parser.add_argument("--port", default=5000, type=int, help="Port to bind")
args = parser.parse_args()
slack_bot = SlackBot()
slack_bot.start(host=args.host, port=args.port)
if __name__ == "__main__":
start_command()
+81
View File
@@ -0,0 +1,81 @@
import argparse
import importlib
import logging
import signal
import sys
from embedchain.helper_classes.json_serializable import register_deserializable
from .base import BaseBot
@register_deserializable
class WhatsAppBot(BaseBot):
def __init__(self):
try:
self.flask = importlib.import_module("flask")
self.twilio = importlib.import_module("twilio")
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for WhatsApp are not installed. "
'Please install with `pip install --upgrade "embedchain[whatsapp]"`'
) from None
super().__init__()
def handle_message(self, message):
if message.startswith("add "):
response = self.add_data(message)
else:
response = self.ask_bot(message)
return response
def add_data(self, message):
data = message.split(" ")[-1]
try:
self.add(data)
response = f"Added data from: {data}"
except Exception:
logging.exception(f"Failed to add data {data}.")
response = "Some error occurred while adding data."
return response
def ask_bot(self, message):
try:
response = self.query(message)
except Exception:
logging.exception(f"Failed to query {message}.")
response = "An error occurred. Please try again!"
return response
def start(self, host="0.0.0.0", port=5000, debug=True):
app = self.flask.Flask(__name__)
def signal_handler(sig, frame):
logging.info("\nGracefully shutting down the WhatsAppBot...")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
@app.route("/chat", methods=["POST"])
def chat():
incoming_message = self.flask.request.values.get("Body", "").lower()
response = self.handle_message(incoming_message)
twilio_response = self.twilio.twiml.messaging_response.MessagingResponse()
twilio_response.message(response)
return str(twilio_response)
app.run(host=host, port=port, debug=debug)
def start_command():
parser = argparse.ArgumentParser(description="EmbedChain WhatsAppBot command line interface")
parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind")
parser.add_argument("--port", default=5000, type=int, help="Port to bind")
args = parser.parse_args()
whatsapp_bot = WhatsAppBot()
whatsapp_bot.start(host=args.host, port=args.port)
if __name__ == "__main__":
start_command()
+16 -1
View File
@@ -1,10 +1,14 @@
import hashlib
from embedchain.helper_classes.json_serializable import JSONSerializable
from embedchain.models.data_type import DataType
class BaseChunker:
class BaseChunker(JSONSerializable):
def __init__(self, text_splitter):
"""Initialize the chunker."""
self.text_splitter = text_splitter
self.data_type = None
def create_chunks(self, loader, src):
"""
@@ -22,7 +26,10 @@ class BaseChunker:
metadatas = []
for data in datas:
content = data["content"]
meta_data = data["meta_data"]
# add data type to meta data to allow query using data type
meta_data["data_type"] = self.data_type.value
url = meta_data["url"]
chunks = self.get_chunks(content)
@@ -47,3 +54,11 @@ class BaseChunker:
Override in child class if custom logic.
"""
return self.text_splitter.split_text(content)
def set_data_type(self, data_type: DataType):
"""
set the data type of chunker
"""
self.data_type = data_type
# TODO: This should be done during initialization. This means it has to be done in the child classes.
+8 -8
View File
@@ -4,19 +4,19 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 500,
"chunk_overlap": 50,
"length_function": len,
}
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class DocsSiteChunker(BaseChunker):
"""Chunker for code docs site."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
config = ChunkerConfig(chunk_size=500, chunk_overlap=50, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+2
View File
@@ -4,8 +4,10 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class DocxFileChunker(BaseChunker):
"""Chunker for .docx file."""
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class NotionChunker(BaseChunker):
"""Chunker for notion."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+2
View File
@@ -4,8 +4,10 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class PdfFileChunker(BaseChunker):
"""Chunker for PDF file."""
+2
View File
@@ -4,8 +4,10 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class QnaPairChunker(BaseChunker):
"""Chunker for QnA pair."""
+20
View File
@@ -0,0 +1,20 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
class TableChunker(BaseChunker):
"""Chunker for tables, for instance csv, google sheets or databases."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+2
View File
@@ -4,8 +4,10 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class TextChunker(BaseChunker):
"""Chunker for text."""
+2
View File
@@ -4,8 +4,10 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class WebPageChunker(BaseChunker):
"""Chunker for web page."""
+2
View File
@@ -4,8 +4,10 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class YoutubeVideoChunker(BaseChunker):
"""Chunker for Youtube video."""
+12
View File
@@ -1,8 +1,10 @@
from typing import Callable, Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class ChunkerConfig(BaseConfig):
"""
Config for the chunker used in `add` method
@@ -19,6 +21,7 @@ class ChunkerConfig(BaseConfig):
self.length_function = length_function if length_function else len
@register_deserializable
class LoaderConfig(BaseConfig):
"""
Config for the chunker used in `add` method
@@ -28,6 +31,7 @@ class LoaderConfig(BaseConfig):
pass
@register_deserializable
class AddConfig(BaseConfig):
"""
Config for the `add` method.
@@ -38,5 +42,13 @@ class AddConfig(BaseConfig):
chunker: Optional[ChunkerConfig] = None,
loader: Optional[LoaderConfig] = None,
):
"""
Initializes a configuration class instance for the `add` method.
:param chunker: Chunker config, defaults to None
:type chunker: Optional[ChunkerConfig], optional
:param loader: Loader config, defaults to None
:type loader: Optional[LoaderConfig], optional
"""
self.loader = loader
self.chunker = chunker
+13 -2
View File
@@ -1,10 +1,21 @@
class BaseConfig:
from typing import Any, Dict
from embedchain.helper_classes.json_serializable import JSONSerializable
class BaseConfig(JSONSerializable):
"""
Base config.
"""
def __init__(self):
"""Initializes a configuration class for a class."""
pass
def as_dict(self):
def as_dict(self) -> Dict[str, Any]:
"""Return config object as a dict
:return: config object as dict
:rtype: Dict[str, Any]
"""
return vars(self)
-80
View File
@@ -1,80 +0,0 @@
from string import Template
from embedchain.config.QueryConfig import QueryConfig
DEFAULT_PROMPT = """
You are a chatbot having a conversation with a human. You are given chat
history and context.
You need to answer the query considering context, chat history and your knowledge base. If you don't know the answer or the answer is neither contained in the context nor in history, then simply say "I don't know".
$context
History: $history
Query: $query
Helpful Answer:
""" # noqa:E501
DEFAULT_PROMPT_TEMPLATE = Template(DEFAULT_PROMPT)
class ChatConfig(QueryConfig):
"""
Config for the `chat` method, inherits from `QueryConfig`.
"""
def __init__(
self,
number_documents=None,
template: Template = None,
model=None,
temperature=None,
max_tokens=None,
top_p=None,
stream: bool = False,
):
"""
Initializes the ChatConfig instance.
:param number_documents: Number of documents to pull from the database as
context.
:param template: Optional. The `Template` instance to use as a template for
prompt.
:param model: Optional. Controls the OpenAI model used.
:param temperature: Optional. Controls the randomness of the model's output.
Higher values (closer to 1) make output more random,lower values make it more
deterministic.
:param max_tokens: Optional. Controls how many tokens are generated.
:param top_p: Optional. Controls the diversity of words.Higher values
(closer to 1) make word selection more diverse, lower values make words less
diverse.
:param stream: Optional. Control if response is streamed back to the user
:raises ValueError: If the template is not valid as template should contain
$context and $query and $history
"""
if template is None:
template = DEFAULT_PROMPT_TEMPLATE
# History is set as 0 to ensure that there is always a history, that way,
# there don't have to be two templates. Having two templates would make it
# complicated because the history is not user controlled.
super().__init__(
number_documents=number_documents,
template=template,
model=model,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
history=[0],
stream=stream,
)
def set_history(self, history):
"""
Chat history is not user provided and not set at initialization time
:param history: (string) history to set
"""
self.history = history
return
-74
View File
@@ -1,74 +0,0 @@
import logging
import os
from chromadb.utils import embedding_functions
from embedchain.config.BaseConfig import BaseConfig
class InitConfig(BaseConfig):
"""
Config to initialize an embedchain `App` instance.
"""
def __init__(self, log_level=None, ef=None, db=None, host=None, port=None, id=None):
"""
:param log_level: Optional. (String) Debug level
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'].
:param ef: Optional. Embedding function to use.
:param db: Optional. (Vector) database to use for embeddings.
:param id: Optional. ID of the app. Document metadata will have this id.
:param host: Optional. Hostname for the database server.
:param port: Optional. Port for the database server.
"""
self._setup_logging(log_level)
self.ef = ef
self.db = db
self.host = host
self.port = port
self.id = id
return
def _set_embedding_function(self, ef):
self.ef = ef
return
def _set_embedding_function_to_default(self):
"""
Sets embedding function to default (`text-embedding-ada-002`).
:raises ValueError: If the template is not valid as template should contain
$context and $query
"""
if os.getenv("OPENAI_API_KEY") is None and os.getenv("OPENAI_ORGANIZATION") is None:
raise ValueError("OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided") # noqa:E501
self.ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name="text-embedding-ada-002",
)
return
def _set_db(self, db):
if db:
self.db = db
return
def _set_db_to_default(self):
"""
Sets database to default (`ChromaDb`).
"""
from embedchain.vectordb.chroma_db import ChromaDB
self.db = ChromaDB(ef=self.ef, host=self.host, port=self.port)
def _setup_logging(self, debug_level):
level = logging.WARNING # Default level
if debug_level is not None:
level = getattr(logging, debug_level.upper(), None)
if not isinstance(level, int):
raise ValueError(f"Invalid log level: {debug_level}")
logging.basicConfig(format="%(asctime)s [%(name)s] [%(levelname)s] %(message)s", level=level)
self.logger = logging.getLogger(__name__)
return
-136
View File
@@ -1,136 +0,0 @@
import re
from string import Template
from embedchain.config.BaseConfig import BaseConfig
DEFAULT_PROMPT = """
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
$context
Query: $query
Helpful Answer:
""" # noqa:E501
DEFAULT_PROMPT_WITH_HISTORY = """
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
I will provide you with our conversation history.
$context
History: $history
Query: $query
Helpful Answer:
""" # noqa:E501
DOCS_SITE_DEFAULT_PROMPT = """
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer. Wherever possible, give complete code snippet. Dont make up any code snippet on your own.
$context
Query: $query
Helpful Answer:
""" # noqa:E501
DEFAULT_PROMPT_TEMPLATE = Template(DEFAULT_PROMPT)
DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE = Template(DEFAULT_PROMPT_WITH_HISTORY)
DOCS_SITE_PROMPT_TEMPLATE = Template(DOCS_SITE_DEFAULT_PROMPT)
query_re = re.compile(r"\$\{*query\}*")
context_re = re.compile(r"\$\{*context\}*")
history_re = re.compile(r"\$\{*history\}*")
class QueryConfig(BaseConfig):
"""
Config for the `query` method.
"""
def __init__(
self,
number_documents=None,
template: Template = None,
model=None,
temperature=None,
max_tokens=None,
top_p=None,
history=None,
stream: bool = False,
):
"""
Initializes the QueryConfig instance.
:param number_documents: Number of documents to pull from the database as
context.
:param template: Optional. The `Template` instance to use as a template for
prompt.
:param model: Optional. Controls the OpenAI model used.
:param temperature: Optional. Controls the randomness of the model's output.
Higher values (closer to 1) make output more random, lower values make it more
deterministic.
:param max_tokens: Optional. Controls how many tokens are generated.
:param top_p: Optional. Controls the diversity of words. Higher values
(closer to 1) make word selection more diverse, lower values make words less
diverse.
:param history: Optional. A list of strings to consider as history.
:param stream: Optional. Control if response is streamed back to user
:raises ValueError: If the template is not valid as template should
contain $context and $query (and optionally $history).
"""
if number_documents is None:
self.number_documents = 1
else:
self.number_documents = number_documents
if not history:
self.history = None
else:
if len(history) == 0:
self.history = None
else:
self.history = history
if template is None:
if self.history is None:
template = DEFAULT_PROMPT_TEMPLATE
else:
template = DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE
self.temperature = temperature if temperature else 0
self.max_tokens = max_tokens if max_tokens else 1000
self.model = model if model else "gpt-3.5-turbo-0613"
self.top_p = top_p if top_p else 1
if self.validate_template(template):
self.template = template
else:
if self.history is None:
raise ValueError("`template` should have `query` and `context` keys")
else:
raise ValueError("`template` should have `query`, `context` and `history` keys")
if not isinstance(stream, bool):
raise ValueError("`stream` should be bool")
self.stream = stream
def validate_template(self, template: Template):
"""
validate the template
:param template: the template to validate
:return: Boolean, valid (true) or invalid (false)
"""
if self.history is None:
return re.search(query_re, template.template) and re.search(context_re, template.template)
else:
return (
re.search(query_re, template.template)
and re.search(context_re, template.template)
and re.search(history_re, template.template)
)
+13 -5
View File
@@ -1,5 +1,13 @@
from .AddConfig import AddConfig, ChunkerConfig # noqa: F401
from .BaseConfig import BaseConfig # noqa: F401
from .ChatConfig import ChatConfig # noqa: F401
from .InitConfig import InitConfig # noqa: F401
from .QueryConfig import QueryConfig # noqa: F401
# flake8: noqa: F401
from .AddConfig import AddConfig, ChunkerConfig
from .apps.AppConfig import AppConfig
from .apps.CustomAppConfig import CustomAppConfig
from .apps.OpenSourceAppConfig import OpenSourceAppConfig
from .BaseConfig import BaseConfig
from .embedder.BaseEmbedderConfig import BaseEmbedderConfig
from .embedder.BaseEmbedderConfig import BaseEmbedderConfig as EmbedderConfig
from .llm.base_llm_config import BaseLlmConfig
from .llm.base_llm_config import BaseLlmConfig as LlmConfig
from .vectordbs.ChromaDbConfig import ChromaDbConfig
from .vectordbs.ElasticsearchDBConfig import ElasticsearchDBConfig
+35
View File
@@ -0,0 +1,35 @@
from typing import Optional
from embedchain.helper_classes.json_serializable import register_deserializable
from .BaseAppConfig import BaseAppConfig
@register_deserializable
class AppConfig(BaseAppConfig):
"""
Config to initialize an embedchain custom `App` instance, with extra config options.
"""
def __init__(
self,
log_level: str = "WARNING",
id: Optional[str] = None,
collect_metrics: Optional[bool] = None,
collection_name: Optional[str] = None,
):
"""
Initializes a configuration class instance for an App. This is the simplest form of an embedchain app.
Most of the configuration is done in the `App` class itself.
:param log_level: Debug level ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], defaults to "WARNING"
:type log_level: str, optional
:param id: ID of the app. Document metadata will have this id., defaults to None
:type id: Optional[str], optional
:param collect_metrics: Send anonymous telemetry to improve embedchain, defaults to True
:type collect_metrics: Optional[bool], optional
:param collection_name: Default collection name. It's recommended to use app.db.set_collection_name() instead,
defaults to None
:type collection_name: Optional[str], optional
"""
super().__init__(log_level=log_level, id=id, collect_metrics=collect_metrics, collection_name=collection_name)
+64
View File
@@ -0,0 +1,64 @@
import logging
from typing import Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.helper_classes.json_serializable import JSONSerializable
from embedchain.vectordb.base_vector_db import BaseVectorDB
class BaseAppConfig(BaseConfig, JSONSerializable):
"""
Parent config to initialize an instance of `App`, `OpenSourceApp` or `CustomApp`.
"""
def __init__(
self,
log_level: str = "WARNING",
db: Optional[BaseVectorDB] = None,
id: Optional[str] = None,
collect_metrics: bool = True,
collection_name: Optional[str] = None,
):
"""
Initializes a configuration class instance for an App.
Most of the configuration is done in the `App` class itself.
:param log_level: Debug level ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], defaults to "WARNING"
:type log_level: str, optional
:param db: A database class. It is recommended to set this directly in the `App` class, not this config,
defaults to None
:type db: Optional[BaseVectorDB], optional
:param id: ID of the app. Document metadata will have this id., defaults to None
:type id: Optional[str], optional
:param collect_metrics: Send anonymous telemetry to improve embedchain, defaults to True
:type collect_metrics: Optional[bool], optional
:param collection_name: Default collection name. It's recommended to use app.db.set_collection_name() instead,
defaults to None
:type collection_name: Optional[str], optional
"""
self._setup_logging(log_level)
self.id = id
self.collect_metrics = True if (collect_metrics is True or collect_metrics is None) else False
self.collection_name = collection_name
if db:
self._db = db
logging.warning(
"DEPRECATION WARNING: Please supply the database as the second parameter during app init. "
"Such as `app(config=config, db=db)`."
)
if collection_name:
logging.warning("DEPRECATION WARNING: Please supply the collection name to the database config.")
return
def _setup_logging(self, debug_level):
level = logging.WARNING # Default level
if debug_level is not None:
level = getattr(logging, debug_level.upper(), None)
if not isinstance(level, int):
raise ValueError(f"Invalid log level: {debug_level}")
logging.basicConfig(format="%(asctime)s [%(name)s] [%(levelname)s] %(message)s", level=level)
self.logger = logging.getLogger(__name__)
return
+46
View File
@@ -0,0 +1,46 @@
from typing import Optional
from dotenv import load_dotenv
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.vectordb.base_vector_db import BaseVectorDB
from .BaseAppConfig import BaseAppConfig
load_dotenv()
@register_deserializable
class CustomAppConfig(BaseAppConfig):
"""
Config to initialize an embedchain custom `App` instance, with extra config options.
"""
def __init__(
self,
log_level: str = "WARNING",
db: Optional[BaseVectorDB] = None,
id: Optional[str] = None,
collect_metrics: Optional[bool] = None,
collection_name: Optional[str] = None,
):
"""
Initializes a configuration class instance for an Custom App.
Most of the configuration is done in the `CustomApp` class itself.
:param log_level: Debug level ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], defaults to "WARNING"
:type log_level: str, optional
:param db: A database class. It is recommended to set this directly in the `CustomApp` class, not this config,
defaults to None
:type db: Optional[BaseVectorDB], optional
:param id: ID of the app. Document metadata will have this id., defaults to None
:type id: Optional[str], optional
:param collect_metrics: Send anonymous telemetry to improve embedchain, defaults to True
:type collect_metrics: Optional[bool], optional
:param collection_name: Default collection name. It's recommended to use app.db.set_collection_name() instead,
defaults to None
:type collection_name: Optional[str], optional
"""
super().__init__(
log_level=log_level, db=db, id=id, collect_metrics=collect_metrics, collection_name=collection_name
)
@@ -0,0 +1,40 @@
from typing import Optional
from embedchain.helper_classes.json_serializable import register_deserializable
from .BaseAppConfig import BaseAppConfig
@register_deserializable
class OpenSourceAppConfig(BaseAppConfig):
"""
Config to initialize an embedchain custom `OpenSourceApp` instance, with extra config options.
"""
def __init__(
self,
log_level: str = "WARNING",
id: Optional[str] = None,
collect_metrics: Optional[bool] = None,
model: str = "orca-mini-3b.ggmlv3.q4_0.bin",
collection_name: Optional[str] = None,
):
"""
Initializes a configuration class instance for an Open Source App.
:param log_level: Debug level ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], defaults to "WARNING"
:type log_level: str, optional
:param id: ID of the app. Document metadata will have this id., defaults to None
:type id: Optional[str], optional
:param collect_metrics: Send anonymous telemetry to improve embedchain, defaults to True
:type collect_metrics: Optional[bool], optional
:param model: GPT4ALL uses the model to instantiate the class.
Unlike `App`, it has to be provided before querying, defaults to "orca-mini-3b.ggmlv3.q4_0.bin"
:type model: str, optional
:param collection_name: Default collection name. It's recommended to use app.db.set_collection_name() instead,
defaults to None
:type collection_name: Optional[str], optional
"""
self.model = model or "orca-mini-3b.ggmlv3.q4_0.bin"
super().__init__(log_level=log_level, id=id, collect_metrics=collect_metrics, collection_name=collection_name)
View File
@@ -0,0 +1,18 @@
from typing import Optional
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class BaseEmbedderConfig:
def __init__(self, model: Optional[str] = None, deployment_name: Optional[str] = None):
"""
Initialize a new instance of an embedder config class.
:param model: model name of the llm embedding model (not applicable to all providers), defaults to None
:type model: Optional[str], optional
:param deployment_name: deployment name for llm embedding model, defaults to None
:type deployment_name: Optional[str], optional
"""
self.model = model
self.deployment_name = deployment_name
View File
+146
View File
@@ -0,0 +1,146 @@
import re
from string import Template
from typing import Any, Dict, Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.helper_classes.json_serializable import register_deserializable
DEFAULT_PROMPT = """
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
$context
Query: $query
Helpful Answer:
""" # noqa:E501
DEFAULT_PROMPT_WITH_HISTORY = """
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
I will provide you with our conversation history.
$context
History: $history
Query: $query
Helpful Answer:
""" # noqa:E501
DOCS_SITE_DEFAULT_PROMPT = """
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer. Wherever possible, give complete code snippet. Dont make up any code snippet on your own.
$context
Query: $query
Helpful Answer:
""" # noqa:E501
DEFAULT_PROMPT_TEMPLATE = Template(DEFAULT_PROMPT)
DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE = Template(DEFAULT_PROMPT_WITH_HISTORY)
DOCS_SITE_PROMPT_TEMPLATE = Template(DOCS_SITE_DEFAULT_PROMPT)
query_re = re.compile(r"\$\{*query\}*")
context_re = re.compile(r"\$\{*context\}*")
history_re = re.compile(r"\$\{*history\}*")
@register_deserializable
class BaseLlmConfig(BaseConfig):
"""
Config for the `query` method.
"""
def __init__(
self,
number_documents: int = 1,
template: Optional[Template] = None,
model: Optional[str] = None,
temperature: float = 0,
max_tokens: int = 1000,
top_p: float = 1,
stream: bool = False,
deployment_name: Optional[str] = None,
system_prompt: Optional[str] = None,
where: Dict[str, Any] = None,
):
"""
Initializes a configuration class instance for the LLM.
Takes the place of the former `QueryConfig` or `ChatConfig`.
Use `LlmConfig` as an alias to `BaseLlmConfig`.
:param number_documents: Number of documents to pull from the database as
context, defaults to 1
:type number_documents: int, optional
:param template: The `Template` instance to use as a template for
prompt, defaults to None
:type template: Optional[Template], optional
:param model: Controls the OpenAI model used, defaults to None
:type model: Optional[str], optional
:param temperature: Controls the randomness of the model's output.
Higher values (closer to 1) make output more random, lower values make it more deterministic, defaults to 0
:type temperature: float, optional
:param max_tokens: Controls how many tokens are generated, defaults to 1000
:type max_tokens: int, optional
:param top_p: Controls the diversity of words. Higher values (closer to 1) make word selection more diverse,
defaults to 1
:type top_p: float, optional
:param stream: Control if response is streamed back to user, defaults to False
:type stream: bool, optional
:param deployment_name: t.b.a., defaults to None
:type deployment_name: Optional[str], optional
:param system_prompt: System prompt string, defaults to None
:type system_prompt: Optional[str], optional
:param where: A dictionary of key-value pairs to filter the database results., defaults to None
:type where: Dict[str, Any], optional
:raises ValueError: If the template is not valid as template should
contain $context and $query (and optionally $history)
:raises ValueError: Stream is not boolean
"""
if template is None:
template = DEFAULT_PROMPT_TEMPLATE
self.number_documents = number_documents
self.temperature = temperature
self.max_tokens = max_tokens
self.model = model
self.top_p = top_p
self.deployment_name = deployment_name
self.system_prompt = system_prompt
if self.validate_template(template):
self.template = template
else:
raise ValueError("`template` should have `query` and `context` keys and potentially `history` (if used).")
if not isinstance(stream, bool):
raise ValueError("`stream` should be bool")
self.stream = stream
self.where = where
def validate_template(self, template: Template) -> bool:
"""
validate the template
:param template: the template to validate
:type template: Template
:return: valid (true) or invalid (false)
:rtype: bool
"""
return re.search(query_re, template.template) and re.search(context_re, template.template)
def _validate_template_history(self, template: Template) -> bool:
"""
validate the template with history
:param template: the template to validate
:type template: Template
:return: valid (true) or invalid (false)
:rtype: bool
"""
return re.search(history_re, template.template)
@@ -0,0 +1,29 @@
from typing import Optional
from embedchain.config.BaseConfig import BaseConfig
class BaseVectorDbConfig(BaseConfig):
def __init__(
self,
collection_name: Optional[str] = None,
dir: str = "db",
host: Optional[str] = None,
port: Optional[str] = None,
):
"""
Initializes a configuration class instance for the vector database.
:param collection_name: Default name for the collection, defaults to None
:type collection_name: Optional[str], optional
:param dir: Path to the database directory, where the database is stored, defaults to "db"
:type dir: str, optional
:param host: Database connection remote host. Use this if you run Embedchain as a client, defaults to None
:type host: Optional[str], optional
:param host: Database connection remote port. Use this if you run Embedchain as a client, defaults to None
:type port: Optional[str], optional
"""
self.collection_name = collection_name or "embedchain_store"
self.dir = dir
self.host = host
self.port = port
@@ -0,0 +1,35 @@
from typing import Optional
from embedchain.config.vectordbs.BaseVectorDbConfig import BaseVectorDbConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class ChromaDbConfig(BaseVectorDbConfig):
def __init__(
self,
collection_name: Optional[str] = None,
dir: Optional[str] = None,
host: Optional[str] = None,
port: Optional[str] = None,
chroma_settings: Optional[dict] = None,
):
"""
Initializes a configuration class instance for ChromaDB.
:param collection_name: Default name for the collection, defaults to None
:type collection_name: Optional[str], optional
:param dir: Path to the database directory, where the database is stored, defaults to None
:type dir: Optional[str], optional
:param host: Database connection remote host. Use this if you run Embedchain as a client, defaults to None
:type host: Optional[str], optional
:param port: Database connection remote port. Use this if you run Embedchain as a client, defaults to None
:type port: Optional[str], optional
:param chroma_settings: Chroma settings dict, defaults to None
:type chroma_settings: Optional[dict], optional
"""
"""
:param chroma_settings: Optional. Chroma settings for connection.
"""
self.chroma_settings = chroma_settings
super().__init__(collection_name=collection_name, dir=dir, host=host, port=port)
@@ -0,0 +1,32 @@
from typing import Dict, List, Optional, Union
from embedchain.config.vectordbs.BaseVectorDbConfig import BaseVectorDbConfig
from embedchain.helper_classes.json_serializable import register_deserializable
@register_deserializable
class ElasticsearchDBConfig(BaseVectorDbConfig):
def __init__(
self,
collection_name: Optional[str] = None,
dir: Optional[str] = None,
es_url: Union[str, List[str]] = None,
**ES_EXTRA_PARAMS: Dict[str, any],
):
"""
Initializes a configuration class instance for an Elasticsearch client.
:param collection_name: Default name for the collection, defaults to None
:type collection_name: Optional[str], optional
:param dir: Path to the database directory, where the database is stored, defaults to None
:type dir: Optional[str], optional
:param es_url: elasticsearch url or list of nodes url to be used for connection, defaults to None
:type es_url: Union[str, List[str]], optional
:param ES_EXTRA_PARAMS: extra params dict that can be passed to elasticsearch.
:type ES_EXTRA_PARAMS: Dict[str, Any], optional
"""
# self, es_url: Union[str, List[str]] = None, **ES_EXTRA_PARAMS: Dict[str, any]):
self.ES_URL = es_url
self.ES_EXTRA_PARAMS = ES_EXTRA_PARAMS
super().__init__(collection_name=collection_name, dir=dir)
+69 -30
View File
@@ -1,11 +1,18 @@
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.chunkers.docs_site import DocsSiteChunker
from embedchain.chunkers.docx_file import DocxFileChunker
from embedchain.chunkers.notion import NotionChunker
from embedchain.chunkers.pdf_file import PdfFileChunker
from embedchain.chunkers.qna_pair import QnaPairChunker
from embedchain.chunkers.table import TableChunker
from embedchain.chunkers.text import TextChunker
from embedchain.chunkers.web_page import WebPageChunker
from embedchain.chunkers.youtube_video import YoutubeVideoChunker
from embedchain.config import AddConfig
from embedchain.config.AddConfig import ChunkerConfig, LoaderConfig
from embedchain.helper_classes.json_serializable import JSONSerializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.loaders.csv import CsvLoader
from embedchain.loaders.docs_site_loader import DocsSiteLoader
from embedchain.loaders.docx_file import DocxFileLoader
from embedchain.loaders.local_qna_pair import LocalQnaPairLoader
@@ -14,61 +21,93 @@ from embedchain.loaders.pdf_file import PdfFileLoader
from embedchain.loaders.sitemap import SitemapLoader
from embedchain.loaders.web_page import WebPageLoader
from embedchain.loaders.youtube_video import YoutubeVideoLoader
from embedchain.models.data_type import DataType
class DataFormatter:
class DataFormatter(JSONSerializable):
"""
DataFormatter is an internal utility class which abstracts the mapping for
loaders and chunkers to the data_type entered by the user in their
.add or .add_local method call
"""
def __init__(self, data_type: str, config: AddConfig):
self.loader = self._get_loader(data_type, config.loader)
self.chunker = self._get_chunker(data_type, config.chunker)
def __init__(self, data_type: DataType, config: AddConfig):
"""
Initialize a dataformatter, set data type and chunker based on datatype.
def _get_loader(self, data_type, config):
:param data_type: The type of the data to load and chunk.
:type data_type: DataType
:param config: AddConfig instance with nested loader and chunker config attributes.
:type config: AddConfig
"""
self.loader = self._get_loader(data_type=data_type, config=config.loader)
self.chunker = self._get_chunker(data_type=data_type, config=config.chunker)
def _get_loader(self, data_type: DataType, config: LoaderConfig) -> BaseLoader:
"""
Returns the appropriate data loader for the given data type.
:param data_type: The type of the data to load.
:return: The loader for the given data type.
:type data_type: DataType
:param config: Config to initialize the loader with.
:type config: LoaderConfig
:raises ValueError: If an unsupported data type is provided.
:return: The loader for the given data type.
:rtype: BaseLoader
"""
loaders = {
"youtube_video": YoutubeVideoLoader(),
"pdf_file": PdfFileLoader(),
"web_page": WebPageLoader(),
"qna_pair": LocalQnaPairLoader(),
"text": LocalTextLoader(),
"docx": DocxFileLoader(),
"sitemap": SitemapLoader(),
"docs_site": DocsSiteLoader(),
DataType.YOUTUBE_VIDEO: YoutubeVideoLoader,
DataType.PDF_FILE: PdfFileLoader,
DataType.WEB_PAGE: WebPageLoader,
DataType.QNA_PAIR: LocalQnaPairLoader,
DataType.TEXT: LocalTextLoader,
DataType.DOCX: DocxFileLoader,
DataType.SITEMAP: SitemapLoader,
DataType.DOCS_SITE: DocsSiteLoader,
DataType.CSV: CsvLoader,
}
lazy_loaders = {DataType.NOTION}
if data_type in loaders:
return loaders[data_type]
loader_class: type = loaders[data_type]
loader: BaseLoader = loader_class()
return loader
elif data_type in lazy_loaders:
if data_type == DataType.NOTION:
from embedchain.loaders.notion import NotionLoader
return NotionLoader()
else:
raise ValueError(f"Unsupported data type: {data_type}")
else:
raise ValueError(f"Unsupported data type: {data_type}")
def _get_chunker(self, data_type, config):
"""
Returns the appropriate chunker for the given data type.
def _get_chunker(self, data_type: DataType, config: ChunkerConfig) -> BaseChunker:
"""Returns the appropriate chunker for the given data type.
:param data_type: The type of the data to chunk.
:return: The chunker for the given data type.
:type data_type: DataType
:param config: Config to initialize the chunker with.
:type config: ChunkerConfig
:raises ValueError: If an unsupported data type is provided.
:return: The chunker for the given data type.
:rtype: BaseChunker
"""
chunkers = {
"youtube_video": YoutubeVideoChunker(config),
"pdf_file": PdfFileChunker(config),
"web_page": WebPageChunker(config),
"qna_pair": QnaPairChunker(config),
"text": TextChunker(config),
"docx": DocxFileChunker(config),
"sitemap": WebPageChunker(config),
"docs_site": DocsSiteChunker(config),
chunker_classes = {
DataType.YOUTUBE_VIDEO: YoutubeVideoChunker,
DataType.PDF_FILE: PdfFileChunker,
DataType.WEB_PAGE: WebPageChunker,
DataType.QNA_PAIR: QnaPairChunker,
DataType.TEXT: TextChunker,
DataType.DOCX: DocxFileChunker,
DataType.WEB_PAGE: WebPageChunker,
DataType.DOCS_SITE: DocsSiteChunker,
DataType.NOTION: NotionChunker,
DataType.CSV: TableChunker,
}
if data_type in chunkers:
return chunkers[data_type]
if data_type in chunker_classes:
chunker_class: type = chunker_classes[data_type]
chunker: BaseChunker = chunker_class(config)
chunker.set_data_type(data_type)
return chunker
else:
raise ValueError(f"Unsupported data type: {data_type}")
+351 -364
View File
@@ -1,110 +1,269 @@
import hashlib
import importlib.metadata
import json
import logging
import os
from string import Template
import threading
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import openai
from chromadb.utils import embedding_functions
import requests
from dotenv import load_dotenv
from langchain.docstore.document import Document
from langchain.memory import ConversationBufferMemory
from tenacity import retry, stop_after_attempt, wait_fixed
from embedchain.config import AddConfig, ChatConfig, InitConfig, QueryConfig
from embedchain.config.QueryConfig import DOCS_SITE_PROMPT_TEMPLATE, DEFAULT_PROMPT, DEFAULT_PROMPT_WITH_HISTORY
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config import AddConfig, BaseLlmConfig
from embedchain.config.apps.BaseAppConfig import BaseAppConfig
from embedchain.data_formatter import DataFormatter
gpt4all_model = None
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.helper_classes.json_serializable import JSONSerializable
from embedchain.llm.base_llm import BaseLlm
from embedchain.loaders.base_loader import BaseLoader
from embedchain.models.data_type import DataType
from embedchain.utils import detect_datatype
from embedchain.vectordb.base_vector_db import BaseVectorDB
load_dotenv()
ABS_PATH = os.getcwd()
DB_DIR = os.path.join(ABS_PATH, "db")
memory = ConversationBufferMemory()
HOME_DIR = str(Path.home())
CONFIG_DIR = os.path.join(HOME_DIR, ".embedchain")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
class EmbedChain:
def __init__(self, config: InitConfig):
class EmbedChain(JSONSerializable):
def __init__(
self,
config: BaseAppConfig,
llm: BaseLlm,
db: BaseVectorDB = None,
embedder: BaseEmbedder = None,
system_prompt: Optional[str] = None,
):
"""
Initializes the EmbedChain instance, sets up a vector DB client and
creates a collection.
:param config: InitConfig instance to load as configuration.
:param config: Configuration just for the app, not the db or llm or embedder.
:type config: BaseAppConfig
:param llm: Instance of the LLM you want to use.
:type llm: BaseLlm
:param db: Instance of the Database to use, defaults to None
:type db: BaseVectorDB, optional
:param embedder: instance of the embedder to use, defaults to None
:type embedder: BaseEmbedder, optional
:param system_prompt: System prompt to use in the llm query, defaults to None
:type system_prompt: Optional[str], optional
:raises ValueError: No database or embedder provided.
"""
self.config = config
self.db_client = self.config.db.client
self.collection = self.config.db.collection
self.user_asks = []
self.is_docs_site_instance = False
self.online = False
def add(self, data_type, url, metadata=None, config: AddConfig = None):
# Add subclasses
## Llm
self.llm = llm
## Database
# Database has support for config assignment for backwards compatibility
if db is None and (not hasattr(self.config, "db") or self.config.db is None):
raise ValueError("App requires Database.")
self.db = db or self.config.db
## Embedder
if embedder is None:
raise ValueError("App requires Embedder.")
self.embedder = embedder
# Initialize database
self.db._set_embedder(self.embedder)
self.db._initialize()
# Set collection name from app config for backwards compatibility.
if config.collection_name:
self.db.set_collection_name(config.collection_name)
# Add variables that are "shortcuts"
if system_prompt:
self.llm.config.system_prompt = system_prompt
# Attributes that aren't subclass related.
self.user_asks = []
# Send anonymous telemetry
self.s_id = self.config.id if self.config.id else str(uuid.uuid4())
self.u_id = self._load_or_generate_user_id()
# NOTE: Uncomment the next two lines when running tests to see if any test fires a telemetry event.
# if (self.config.collect_metrics):
# raise ConnectionRefusedError("Collection of metrics should not be allowed.")
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("init",))
thread_telemetry.start()
def _load_or_generate_user_id(self) -> str:
"""
Loads the user id from the config file if it exists, otherwise generates a new
one and saves it to the config file.
:return: user id
:rtype: str
"""
if not os.path.exists(CONFIG_DIR):
os.makedirs(CONFIG_DIR)
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r") as f:
data = json.load(f)
if "user_id" in data:
return data["user_id"]
u_id = str(uuid.uuid4())
with open(CONFIG_FILE, "w") as f:
json.dump({"user_id": u_id}, f)
return u_id
def add(
self,
source: Any,
data_type: Optional[DataType] = None,
metadata: Optional[Dict[str, Any]] = None,
config: Optional[AddConfig] = None,
):
"""
Adds the data from the given URL to the vector db.
Loads the data, chunks it, create embedding for each chunk
and then stores the embedding to vector database.
:param data_type: The type of the data to add.
:param url: The URL where the data is located.
:param metadata: Optional. Metadata associated with the data source.
:param config: Optional. The `AddConfig` instance to use as configuration
options.
:param source: The data to embed, can be a URL, local file or raw content, depending on the data type.
:type source: Any
:param data_type: Automatically detected, but can be forced with this argument. The type of the data to add,
defaults to None
:type data_type: Optional[DataType], optional
:param metadata: Metadata associated with the data source., defaults to None
:type metadata: Optional[Dict[str, Any]], optional
:param config: The `AddConfig` instance to use as configuration options., defaults to None
:type config: Optional[AddConfig], optional
:raises ValueError: Invalid data type
:return: source_id, a md5-hash of the source, in hexadecimal representation.
:rtype: str
"""
if config is None:
config = AddConfig()
try:
DataType(source)
logging.warning(
f"""Starting from version v0.0.40, Embedchain can automatically detect the data type. So, in the `add` method, the argument order has changed. You no longer need to specify '{source}' for the `source` argument. So the code snippet will be `.add("{data_type}", "{source}")`""" # noqa #E501
)
logging.warning(
"Embedchain is swapping the arguments for you. This functionality might be deprecated in the future, so please adjust your code." # noqa #E501
)
source, data_type = data_type, source
except ValueError:
pass
if data_type:
try:
data_type = DataType(data_type)
except ValueError:
raise ValueError(
f"Invalid data_type: '{data_type}'.",
f"Please use one of the following: {[data_type.value for data_type in DataType]}",
) from None
if not data_type:
data_type = detect_datatype(source)
# `source_id` is the hash of the source argument
hash_object = hashlib.md5(str(source).encode("utf-8"))
source_id = hash_object.hexdigest()
data_formatter = DataFormatter(data_type, config)
self.user_asks.append([data_type, url, metadata])
self.load_and_embed(data_formatter.loader, data_formatter.chunker, url, metadata)
if data_type in ("docs_site",):
self.user_asks.append([source, data_type.value, metadata])
documents, _metadatas, _ids, new_chunks = self.load_and_embed(
data_formatter.loader, data_formatter.chunker, source, metadata, source_id
)
if data_type in {DataType.DOCS_SITE}:
self.is_docs_site_instance = True
def add_local(self, data_type, content, metadata=None, config: AddConfig = None):
# Send anonymous telemetry
if self.config.collect_metrics:
# it's quicker to check the variable twice than to count words when they won't be submitted.
word_count = sum([len(document.split(" ")) for document in documents])
extra_metadata = {"data_type": data_type.value, "word_count": word_count, "chunks_count": new_chunks}
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("add", extra_metadata))
thread_telemetry.start()
return source_id
def add_local(
self,
source: Any,
data_type: Optional[DataType] = None,
metadata: Optional[Dict[str, Any]] = None,
config: Optional[AddConfig] = None,
):
"""
Adds the data you supply to the vector db.
Adds the data from the given URL to the vector db.
Loads the data, chunks it, create embedding for each chunk
and then stores the embedding to vector database.
:param data_type: The type of the data to add.
:param content: The local data. Refer to the `README` for formatting.
:param metadata: Optional. Metadata associated with the data source.
:param config: Optional. The `AddConfig` instance to use as
configuration options.
"""
if config is None:
config = AddConfig()
Warning:
This method is deprecated and will be removed in future versions. Use `add` instead.
data_formatter = DataFormatter(data_type, config)
self.user_asks.append([data_type, content])
self.load_and_embed(
data_formatter.loader,
data_formatter.chunker,
content,
metadata,
:param source: The data to embed, can be a URL, local file or raw content, depending on the data type.
:type source: Any
:param data_type: Automatically detected, but can be forced with this argument. The type of the data to add,
defaults to None
:type data_type: Optional[DataType], optional
:param metadata: Metadata associated with the data source., defaults to None
:type metadata: Optional[Dict[str, Any]], optional
:param config: The `AddConfig` instance to use as configuration options., defaults to None
:type config: Optional[AddConfig], optional
:raises ValueError: Invalid data type
:return: source_id, a md5-hash of the source, in hexadecimal representation.
:rtype: str
"""
logging.warning(
"The `add_local` method is deprecated and will be removed in future versions. Please use the `add` method for both local and remote files." # noqa: E501
)
return self.add(source=source, data_type=data_type, metadata=metadata, config=config)
def load_and_embed(self, loader, chunker, src, metadata=None):
"""
Loads the data from the given URL, chunks it, and adds it to database.
def load_and_embed(
self,
loader: BaseLoader,
chunker: BaseChunker,
src: Any,
metadata: Optional[Dict[str, Any]] = None,
source_id: Optional[str] = None,
) -> Tuple[List[str], Dict[str, Any], List[str], int]:
"""The loader to use to load the data.
:param loader: The loader to use to load the data.
:type loader: BaseLoader
:param chunker: The chunker to use to chunk the data.
:param src: The data to be handled by the loader. Can be a URL for
remote sources or local content for local loaders.
:param metadata: Optional. Metadata associated with the data source.
:type chunker: BaseChunker
:param src: The data to be handled by the loader.
Can be a URL for remote sources or local content for local loaders.
:type src: Any
:param metadata: Metadata associated with the data source., defaults to None
:type metadata: Dict[str, Any], optional
:param source_id: Hexadecimal hash of the source., defaults to None
:type source_id: str, optional
:return: (List) documents (embedded text), (List) metadata, (list) ids, (int) number of chunks
:rtype: Tuple[List[str], Dict[str, Any], List[str], int]
"""
embeddings_data = chunker.create_chunks(loader, src)
# spread chunking results
documents = embeddings_data["documents"]
metadatas = embeddings_data["metadatas"]
ids = embeddings_data["ids"]
# get existing ids, and discard doc if any common id exist.
where = {"app_id": self.config.id} if self.config.id is not None else {}
# where={"url": src}
existing_docs = self.collection.get(
existing_ids = self.db.get(
ids=ids,
where=where, # optional filter
)
existing_ids = set(existing_docs["ids"])
if len(existing_ids):
data_dict = {id: (doc, meta) for id, doc, meta in zip(ids, documents, metadatas)}
@@ -112,369 +271,197 @@ class EmbedChain:
if not data_dict:
print(f"All data from {src} already exists in the database.")
return
# Make sure to return a matching return type
return [], [], [], 0
ids = list(data_dict.keys())
documents, metadatas = zip(*data_dict.values())
# Add app id in metadatas so that they can be queried on later
if self.config.id is not None:
metadatas = [{**m, "app_id": self.config.id} for m in metadatas]
# Loop though all metadatas and add extras.
new_metadatas = []
for m in metadatas:
# Add app id in metadatas so that they can be queried on later
if self.config.id:
m["app_id"] = self.config.id
chunks_before_addition = self.count()
# Add hashed source
m["hash"] = source_id
# Add metadata to each document
metadatas_with_metadata = [meta or metadata for meta in metadatas]
# Note: Metadata is the function argument
if metadata:
# Spread whatever is in metadata into the new object.
m.update(metadata)
self.collection.add(documents=documents, metadatas=list(metadatas_with_metadata), ids=ids)
print((f"Successfully saved {src}. New chunks count: " f"{self.count() - chunks_before_addition}"))
new_metadatas.append(m)
metadatas = new_metadatas
def _format_result(self, results):
return [
(Document(page_content=result[0], metadata=result[1] or {}), result[2])
for result in zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0],
)
]
# Count before, to calculate a delta in the end.
chunks_before_addition = self.db.count()
def get_llm_model_answer(self, prompt):
raise NotImplementedError
self.db.add(documents=documents, metadatas=metadatas, ids=ids)
count_new_chunks = self.db.count() - chunks_before_addition
print((f"Successfully saved {src} ({chunker.data_type}). New chunks count: {count_new_chunks}"))
return list(documents), metadatas, ids, count_new_chunks
def retrieve_from_database(self, input_query, config: QueryConfig):
def retrieve_from_database(self, input_query: str, config: Optional[BaseLlmConfig] = None, where=None) -> List[str]:
"""
Queries the vector database based on the given input query.
Gets relevant doc based on the query
:param input_query: The query to use.
:param config: The query configuration.
:return: The content of the document that matched your query.
:type input_query: str
:param config: The query configuration, defaults to None
:type config: Optional[BaseLlmConfig], optional
:param where: A dictionary of key-value pairs to filter the database results, defaults to None
:type where: _type_, optional
:return: List of contents of the document that matched your query
:rtype: List[str]
"""
where = {"app_id": self.config.id} if self.config.id is not None else {} # optional filter
result = self.collection.query(
query_texts=[
input_query,
],
n_results=config.number_documents,
query_config = config or self.llm.config
if where is not None:
where = where
elif query_config is not None and query_config.where is not None:
where = query_config.where
else:
where = {}
if self.config.id is not None:
where.update({"app_id": self.config.id})
contents = self.db.query(
input_query=input_query,
n_results=query_config.number_documents,
where=where,
)
results_formatted = self._format_result(result)
contents = [result[0].page_content for result in results_formatted]
return contents
def _append_search_and_context(self, context, web_search_result):
return f"{context}\nWeb Search Result: {web_search_result}"
def generate_prompt(self, input_query, contexts, config: QueryConfig, **kwargs):
"""
Generates a prompt based on the given query and context, ready to be
passed to an LLM
:param input_query: The query to use.
:param contexts: List of similar documents to the query used as context.
:param config: Optional. The `QueryConfig` instance to use as
configuration options.
:return: The prompt
"""
context_string = (" | ").join(contexts)
web_search_result = kwargs.get("web_search_result", "")
if web_search_result:
context_string = self._append_search_and_context(context_string, web_search_result)
if not config.history:
prompt = config.template.substitute(context=context_string, query=input_query)
else:
prompt = config.template.substitute(context=context_string, query=input_query, history=config.history)
return prompt
def get_answer_from_llm(self, prompt, config: ChatConfig):
"""
Gets an answer based on the given query and context by passing it
to an LLM.
:param query: The query to use.
:param context: Similar documents to the query used as context.
:return: The answer.
"""
return self.get_llm_model_answer(prompt, config)
def access_search_and_get_results(self, input_query):
from langchain.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
logging.info(f"Access search to get answers for {input_query}")
return search.run(input_query)
def query(self, input_query, config: QueryConfig = None):
def query(self, input_query: str, config: BaseLlmConfig = None, dry_run=False, where: Optional[Dict] = None) -> str:
"""
Queries the vector database based on the given input query.
Gets relevant doc based on the query and then passes it to an
LLM as context to get the answer.
:param input_query: The query to use.
:param config: Optional. The `QueryConfig` instance to use as
configuration options.
:return: The answer to the query.
:type input_query: str
:param config: The `LlmConfig` instance to use as configuration options. This is used for one method call.
To persistently use a config, declare it during app init., defaults to None
:type config: Optional[BaseLlmConfig], optional
:param dry_run: A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response., defaults to False
:type dry_run: bool, optional
:param where: A dictionary of key-value pairs to filter the database results., defaults to None
:type where: Optional[Dict[str, str]], optional
:return: The answer to the query or the dry run result
:rtype: str
"""
if config is None:
config = QueryConfig()
if self.is_docs_site_instance:
config.template = DOCS_SITE_PROMPT_TEMPLATE
config.number_documents = 5
k = {}
if self.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
contexts = self.retrieve_from_database(input_query, config)
prompt = self.generate_prompt(input_query, contexts, config, **k)
logging.info(f"Prompt: {prompt}")
contexts = self.retrieve_from_database(input_query=input_query, config=config, where=where)
answer = self.llm.query(input_query=input_query, contexts=contexts, config=config, dry_run=dry_run)
answer = self.get_answer_from_llm(prompt, config)
# Send anonymous telemetry
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("query",))
thread_telemetry.start()
if isinstance(answer, str):
logging.info(f"Answer: {answer}")
return answer
else:
return self._stream_query_response(answer)
return answer
def _stream_query_response(self, answer):
streamed_answer = ""
for chunk in answer:
streamed_answer = streamed_answer + chunk
yield chunk
logging.info(f"Answer: {streamed_answer}")
def chat(self, input_query, config: ChatConfig = None):
def chat(
self,
input_query: str,
config: Optional[BaseLlmConfig] = None,
dry_run=False,
where: Optional[Dict[str, str]] = None,
) -> str:
"""
Queries the vector database on the given input query.
Gets relevant doc based on the query and then passes it to an
LLM as context to get the answer.
Maintains the whole conversation in memory.
:param input_query: The query to use.
:param config: Optional. The `ChatConfig` instance to use as
configuration options.
:return: The answer to the query.
"""
if config is None:
config = ChatConfig()
if self.is_docs_site_instance:
config.template = DOCS_SITE_PROMPT_TEMPLATE
config.number_documents = 5
k = {}
if self.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
contexts = self.retrieve_from_database(input_query, config, **k)
global memory
chat_history = memory.load_memory_variables({})["history"]
if chat_history:
config.set_history(chat_history)
prompt = self.generate_prompt(input_query, contexts, config, **k)
logging.info(f"Prompt: {prompt}")
answer = self.get_answer_from_llm(prompt, config)
memory.chat_memory.add_user_message(input_query)
if isinstance(answer, str):
memory.chat_memory.add_ai_message(answer)
logging.info(f"Answer: {answer}")
return answer
else:
# this is a streamed response and needs to be handled differently.
return self._stream_chat_response(answer)
def _stream_chat_response(self, answer):
streamed_answer = ""
for chunk in answer:
streamed_answer = streamed_answer + chunk
yield chunk
memory.chat_memory.add_ai_message(streamed_answer)
logging.info(f"Answer: {streamed_answer}")
def dry_run(self, input_query, config: QueryConfig = None):
"""
A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response.
You can use it to test your prompt, including the context provided
by the vector database's doc retrieval.
The only thing the dry run does not consider is the cut-off due to
the `max_tokens` parameter.
:param input_query: The query to use.
:param config: Optional. The `QueryConfig` instance to use as
configuration options.
:return: The prompt that would be sent to the LLM
:type input_query: str
:param config: The `LlmConfig` instance to use as configuration options. This is used for one method call.
To persistently use a config, declare it during app init., defaults to None
:type config: Optional[BaseLlmConfig], optional
:param dry_run: A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response., defaults to False
:type dry_run: bool, optional
:param where: A dictionary of key-value pairs to filter the database results., defaults to None
:type where: Optional[Dict[str, str]], optional
:return: The answer to the query or the dry run result
:rtype: str
"""
if config is None:
config = QueryConfig()
contexts = self.retrieve_from_database(input_query, config)
prompt = self.generate_prompt(input_query, contexts, config)
logging.info(f"Prompt: {prompt}")
return prompt
contexts = self.retrieve_from_database(input_query=input_query, config=config, where=where)
answer = self.llm.chat(input_query=input_query, contexts=contexts, config=config, dry_run=dry_run)
def count(self):
# Send anonymous telemetry
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("chat",))
thread_telemetry.start()
return answer
def set_collection_name(self, name: str):
"""
Set the name of the collection. A collection is an isolated space for vectors.
Using `app.db.set_collection_name` method is preferred to this.
:param name: Name of the collection.
:type name: str
"""
self.db.set_collection_name(name)
# Create the collection if it does not exist
self.db._get_or_create_collection(name)
# TODO: Check whether it is necessary to assign to the `self.collection` attribute,
# since the main purpose is the creation.
def count(self) -> int:
"""
Count the number of embeddings.
DEPRECATED IN FAVOR OF `db.count()`
:return: The number of embeddings.
:rtype: int
"""
return self.collection.count()
logging.warning("DEPRECATION WARNING: Please use `app.db.count()` instead of `app.count()`.")
return self.db.count()
def reset(self):
"""
Resets the database. Deletes all embeddings irreversibly.
`App` has to be reinitialized after using this method.
`App` does not have to be reinitialized after using this method.
DEPRECATED IN FAVOR OF `db.reset()`
"""
self.db_client.reset()
# Send anonymous telemetry
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("reset",))
thread_telemetry.start()
logging.warning("DEPRECATION WARNING: Please use `app.db.reset()` instead of `App.reset()`.")
self.db.reset()
class App(EmbedChain):
"""
The EmbedChain app.
Has two functions: add and query.
adds(data_type, url): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
dry_run(query): test your prompt without consuming tokens.
"""
def __init__(self, config: InitConfig = None):
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
def _send_telemetry_event(self, method: str, extra_metadata: Optional[dict] = None):
"""
:param config: InitConfig instance to load as configuration. Optional.
Send telemetry event to the embedchain server. This is anonymous. It can be toggled off in `AppConfig`.
"""
if config is None:
config = InitConfig()
if not self.config.collect_metrics:
return
if not config.ef:
config._set_embedding_function_to_default()
with threading.Lock():
url = "https://api.embedchain.ai/api/v1/telemetry/"
metadata = {
"s_id": self.s_id,
"version": importlib.metadata.version(__package__ or __name__),
"method": method,
"language": "py",
"u_id": self.u_id,
}
if extra_metadata:
metadata.update(extra_metadata)
if not config.db:
config._set_db_to_default()
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig):
messages = []
messages.append({"role": "user", "content": prompt})
response = openai.ChatCompletion.create(
model=config.model,
messages=messages,
temperature=config.temperature,
max_tokens=config.max_tokens,
top_p=config.top_p,
stream=config.stream,
)
if config.stream:
return self._stream_llm_model_response(response)
else:
return response["choices"][0]["message"]["content"]
def _stream_llm_model_response(self, response):
"""
This is a generator for streaming response from the OpenAI completions API
"""
for line in response:
chunk = line["choices"][0].get("delta", {}).get("content", "")
yield chunk
class OpenSourceApp(EmbedChain):
"""
The OpenSource app.
Same as App, but uses an open source embedding model and LLM.
Has two function: add and query.
adds(data_type, url): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
"""
def __init__(self, config: InitConfig = None):
"""
:param config: InitConfig instance to load as configuration. Optional.
`ef` defaults to open source.
"""
print("Loading open source embedding model. This may take some time...") # noqa:E501
if not config:
config = InitConfig()
if not config.ef:
config._set_embedding_function(
embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
)
if not config.db:
config._set_db_to_default()
print("Successfully loaded open source embedding model.")
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig):
from gpt4all import GPT4All
global gpt4all_model
if gpt4all_model is None:
gpt4all_model = GPT4All("orca-mini-3b.ggmlv3.q4_0.bin")
response = gpt4all_model.generate(prompt=prompt, streaming=config.stream)
return response
class EmbedChainPersonApp:
"""
Base class to create a person bot.
This bot behaves and speaks like a person.
:param person: name of the person, better if its a well known person.
:param config: InitConfig instance to load as configuration.
"""
def __init__(self, person, config: InitConfig = None):
self.person = person
self.person_prompt = f"You are {person}. Whatever you say, you will always say in {person} style." # noqa:E501
if config is None:
config = InitConfig()
super().__init__(config)
class PersonApp(EmbedChainPersonApp, App):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and App
"""
def query(self, input_query, config: QueryConfig = None):
self.template = Template(self.person_prompt + " " + DEFAULT_PROMPT)
query_config = QueryConfig(
template=self.template,
)
return super().query(input_query, query_config)
def chat(self, input_query, config: ChatConfig = None):
self.template = Template(self.person_prompt + " " + DEFAULT_PROMPT_WITH_HISTORY)
chat_config = ChatConfig(
template=self.template,
)
return super().chat(input_query, chat_config)
class PersonOpenSourceApp(EmbedChainPersonApp, OpenSourceApp):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and OpenSourceApp
"""
def query(self, input_query, config: QueryConfig = None):
query_config = QueryConfig(
template=self.template,
)
return super().query(input_query, query_config)
def chat(self, input_query, config: ChatConfig = None):
chat_config = ChatConfig(
template=self.template,
)
return super().chat(input_query, chat_config)
response = requests.post(url, json={"metadata": metadata})
if response.status_code != 200:
logging.warning(f"Telemetry event failed with status code {response.status_code}")
View File
+70
View File
@@ -0,0 +1,70 @@
from typing import Any, Callable, Optional
from embedchain.config.embedder.BaseEmbedderConfig import BaseEmbedderConfig
try:
from chromadb.api.types import Documents, Embeddings
except RuntimeError:
from embedchain.utils import use_pysqlite3
use_pysqlite3()
from chromadb.api.types import Documents, Embeddings
class BaseEmbedder:
"""
Class that manages everything regarding embeddings. Including embedding function, loaders and chunkers.
Embedding functions and vector dimensions are set based on the child class you choose.
To manually overwrite you can use this classes `set_...` methods.
"""
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
"""
Intialize the embedder class.
:param config: embedder configuration option class, defaults to None
:type config: Optional[BaseEmbedderConfig], optional
"""
if config is None:
self.config = BaseEmbedderConfig()
else:
self.config = config
self.vector_dimension: int
def set_embedding_fn(self, embedding_fn: Callable[[list[str]], list[str]]):
"""
Set or overwrite the embedding function to be used by the database to store and retrieve documents.
:param embedding_fn: Function to be used to generate embeddings.
:type embedding_fn: Callable[[list[str]], list[str]]
:raises ValueError: Embedding function is not callable.
"""
if not hasattr(embedding_fn, "__call__"):
raise ValueError("Embedding function is not a function")
self.embedding_fn = embedding_fn
def set_vector_dimension(self, vector_dimension: int):
"""
Set or overwrite the vector dimension size
:param vector_dimension: vector dimension size
:type vector_dimension: int
"""
self.vector_dimension = vector_dimension
@staticmethod
def _langchain_default_concept(embeddings: Any):
"""
Langchains default function layout for embeddings.
:param embeddings: Langchain embeddings
:type embeddings: Any
:return: embedding function
:rtype: Callable
"""
def embed_function(texts: Documents) -> Embeddings:
return embeddings.embed_documents(texts)
return embed_function
+21
View File
@@ -0,0 +1,21 @@
from typing import Optional
from chromadb.utils import embedding_functions
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.models import EmbeddingFunctions
class GPT4AllEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
# Note: We could use langchains GPT4ALL embedding, but it's not available in all versions.
super().__init__(config=config)
if self.config.model is None:
self.config.model = "all-MiniLM-L6-v2"
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=self.config.model)
self.set_embedding_fn(embedding_fn=embedding_fn)
vector_dimension = EmbeddingFunctions.GPT4ALL.value
self.set_vector_dimension(vector_dimension=vector_dimension)
@@ -0,0 +1,19 @@
from typing import Optional
from langchain.embeddings import HuggingFaceEmbeddings
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.models import EmbeddingFunctions
class HuggingFaceEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
embeddings = HuggingFaceEmbeddings(model_name=self.config.model)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
self.set_embedding_fn(embedding_fn=embedding_fn)
vector_dimension = EmbeddingFunctions.HUGGING_FACE.value
self.set_vector_dimension(vector_dimension=vector_dimension)
+40
View File
@@ -0,0 +1,40 @@
import os
from typing import Optional
from langchain.embeddings import OpenAIEmbeddings
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.models import EmbeddingFunctions
try:
from chromadb.utils import embedding_functions
except RuntimeError:
from embedchain.utils import use_pysqlite3
use_pysqlite3()
from chromadb.utils import embedding_functions
class OpenAiEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
if self.config.model is None:
self.config.model = "text-embedding-ada-002"
if self.config.deployment_name:
embeddings = OpenAIEmbeddings(deployment=self.config.deployment_name)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
else:
if os.getenv("OPENAI_API_KEY") is None and os.getenv("OPENAI_ORGANIZATION") is None:
raise ValueError(
"OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided"
) # noqa:E501
embedding_fn = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name=self.config.model,
)
self.set_embedding_fn(embedding_fn=embedding_fn)
self.set_vector_dimension(vector_dimension=EmbeddingFunctions.OPENAI.value)
+19
View File
@@ -0,0 +1,19 @@
from typing import Optional
from langchain.embeddings import VertexAIEmbeddings
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.models import EmbeddingFunctions
class VertexAiEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
embeddings = VertexAIEmbeddings(model_name=config.model)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
self.set_embedding_fn(embedding_fn=embedding_fn)
vector_dimension = EmbeddingFunctions.VERTEX_AI.value
self.set_vector_dimension(vector_dimension=vector_dimension)
@@ -0,0 +1,180 @@
import json
import logging
from typing import Any, Dict, Type, TypeVar, Union
T = TypeVar("T", bound="JSONSerializable")
# NOTE: Through inheritance, all of our classes should be children of JSONSerializable. (highest level)
# NOTE: The @register_deserializable decorator should be added to all user facing child classes. (lowest level)
def register_deserializable(cls: Type[T]) -> Type[T]:
"""
A class decorator to register a class as deserializable.
When a class is decorated with @register_deserializable, it becomes
a part of the set of classes that the JSONSerializable class can
deserialize.
Deserialization is in essence loading attributes from a json file.
This decorator is a security measure put in place to make sure that
you don't load attributes that were initially part of another class.
Example:
@register_deserializable
class ChildClass(JSONSerializable):
def __init__(self, ...):
# initialization logic
Args:
cls (Type): The class to be registered.
Returns:
Type: The same class, after registration.
"""
JSONSerializable.register_class_as_deserializable(cls)
return cls
class JSONSerializable:
"""
A class to represent a JSON serializable object.
This class provides methods to serialize and deserialize objects,
as well as save serialized objects to a file and load them back.
"""
_deserializable_classes = set() # Contains classes that are whitelisted for deserialization.
def serialize(self) -> str:
"""
Serialize the object to a JSON-formatted string.
Returns:
str: A JSON string representation of the object.
"""
try:
return json.dumps(self, default=self._auto_encoder, ensure_ascii=False)
except Exception as e:
logging.error(f"Serialization error: {e}")
return "{}"
@classmethod
def deserialize(cls, json_str: str) -> Any:
"""
Deserialize a JSON-formatted string to an object.
If it fails, a default class is returned instead.
Note: This *returns* an instance, it's not automatically loaded on the calling class.
Example:
app = App.deserialize(json_str)
Args:
json_str (str): A JSON string representation of an object.
Returns:
Object: The deserialized object.
"""
try:
return json.loads(json_str, object_hook=cls._auto_decoder)
except Exception as e:
logging.error(f"Deserialization error: {e}")
# Return a default instance in case of failure
return cls()
@staticmethod
def _auto_encoder(obj: Any) -> Union[Dict[str, Any], None]:
"""
Automatically encode an object for JSON serialization.
Args:
obj (Object): The object to be encoded.
Returns:
dict: A dictionary representation of the object.
"""
if hasattr(obj, "__dict__"):
dct = obj.__dict__.copy()
for key, value in list(
dct.items()
): # We use list() to get a copy of items to avoid dictionary size change during iteration.
try:
# Recursive: If the value is an instance of a subclass of JSONSerializable,
# serialize it using the JSONSerializable serialize method.
if isinstance(value, JSONSerializable):
serialized_value = value.serialize()
# The value is stored as a serialized string.
dct[key] = json.loads(serialized_value)
else:
json.dumps(value) # Try to serialize the value.
except TypeError:
del dct[key] # If it fails, remove the key-value pair from the dictionary.
dct["__class__"] = obj.__class__.__name__
return dct
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
@classmethod
def _auto_decoder(cls, dct: Dict[str, Any]) -> Any:
"""
Automatically decode a dictionary to an object during JSON deserialization.
Args:
dct (dict): The dictionary representation of an object.
Returns:
Object: The decoded object or the original dictionary if decoding is not possible.
"""
class_name = dct.pop("__class__", None)
if class_name:
if not hasattr(cls, "_deserializable_classes"): # Additional safety check
raise AttributeError(f"`{class_name}` has no registry of allowed deserializations.")
if class_name not in {cl.__name__ for cl in cls._deserializable_classes}:
raise KeyError(f"Deserialization of class `{class_name}` is not allowed.")
target_class = next((cl for cl in cls._deserializable_classes if cl.__name__ == class_name), None)
if target_class:
obj = target_class.__new__(target_class)
for key, value in dct.items():
default_value = getattr(target_class, key, None)
setattr(obj, key, value or default_value)
return obj
return dct
def save_to_file(self, filename: str) -> None:
"""
Save the serialized object to a file.
Args:
filename (str): The path to the file where the object should be saved.
"""
with open(filename, "w", encoding="utf-8") as f:
f.write(self.serialize())
@classmethod
def load_from_file(cls, filename: str) -> Any:
"""
Load and deserialize an object from a file.
Args:
filename (str): The path to the file from which the object should be loaded.
Returns:
Object: The deserialized object.
"""
with open(filename, "r", encoding="utf-8") as f:
json_str = f.read()
return cls.deserialize(json_str)
@classmethod
def register_class_as_deserializable(cls, target_class: Type[T]) -> None:
"""
Register a class as deserializable. This is a classmethod and globally shared.
This method adds the target class to the set of classes that
can be deserialized. This is a security measure to ensure only
whitelisted classes are deserialized.
Args:
target_class (Type): The class to be registered.
"""
cls._deserializable_classes.add(target_class)
View File
+28
View File
@@ -0,0 +1,28 @@
import logging
from typing import Optional
from embedchain.config import BaseLlmConfig
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
@register_deserializable
class AntrophicLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return AntrophicLlm._get_athrophic_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_athrophic_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain.chat_models import ChatAnthropic
chat = ChatAnthropic(temperature=config.temperature, model=config.model)
if config.max_tokens and config.max_tokens != 1000:
logging.warning("Config option `max_tokens` is not supported by this model.")
messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
+38
View File
@@ -0,0 +1,38 @@
import logging
from typing import Optional
from embedchain.config import BaseLlmConfig
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
@register_deserializable
class AzureOpenAiLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return AzureOpenAiLlm._get_azure_openai_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_azure_openai_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain.chat_models import AzureChatOpenAI
if not config.deployment_name:
raise ValueError("Deployment name must be provided for Azure OpenAI")
chat = AzureChatOpenAI(
deployment_name=config.deployment_name,
openai_api_version="2023-05-15",
model_name=config.model or "gpt-3.5-turbo",
temperature=config.temperature,
max_tokens=config.max_tokens,
streaming=config.stream,
)
if config.top_p and config.top_p != 1:
logging.warning("Config option `top_p` is not supported by this model.")
messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
+272
View File
@@ -0,0 +1,272 @@
import logging
from typing import Any, Dict, Generator, List, Optional
from langchain.memory import ConversationBufferMemory
from langchain.schema import BaseMessage
from embedchain.config import BaseLlmConfig
from embedchain.config.llm.base_llm_config import (
DEFAULT_PROMPT, DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE,
DOCS_SITE_PROMPT_TEMPLATE)
from embedchain.helper_classes.json_serializable import JSONSerializable
class BaseLlm(JSONSerializable):
def __init__(self, config: Optional[BaseLlmConfig] = None):
"""Initialize a base LLM class
:param config: LLM configuration option class, defaults to None
:type config: Optional[BaseLlmConfig], optional
"""
if config is None:
self.config = BaseLlmConfig()
else:
self.config = config
self.memory = ConversationBufferMemory()
self.is_docs_site_instance = False
self.online = False
self.history: Any = None
def get_llm_model_answer(self):
"""
Usually implemented by child class
"""
raise NotImplementedError
def set_history(self, history: Any):
"""
Provide your own history.
Especially interesting for the query method, which does not internally manage conversation history.
:param history: History to set
:type history: Any
"""
self.history = history
def update_history(self):
"""Update class history attribute with history in memory (for chat method)"""
chat_history = self.memory.load_memory_variables({})["history"]
if chat_history:
self.set_history(chat_history)
def generate_prompt(self, input_query: str, contexts: List[str], **kwargs: Dict[str, Any]) -> str:
"""
Generates a prompt based on the given query and context, ready to be
passed to an LLM
:param input_query: The query to use.
:type input_query: str
:param contexts: List of similar documents to the query used as context.
:type contexts: List[str]
:return: The prompt
:rtype: str
"""
context_string = (" | ").join(contexts)
web_search_result = kwargs.get("web_search_result", "")
if web_search_result:
context_string = self._append_search_and_context(context_string, web_search_result)
if not self.history:
prompt = self.config.template.substitute(context=context_string, query=input_query)
else:
# check if it's the default template without history
if (
not self.config._validate_template_history(self.config.template)
and self.config.template.template == DEFAULT_PROMPT
):
# swap in the template with history
prompt = DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE.substitute(
context=context_string, query=input_query, history=self.history
)
elif not self.config._validate_template_history(self.config.template):
logging.warning("Template does not include `$history` key. History is not included in prompt.")
prompt = self.config.template.substitute(context=context_string, query=input_query)
else:
prompt = self.config.template.substitute(
context=context_string, query=input_query, history=self.history
)
return prompt
def _append_search_and_context(self, context: str, web_search_result: str) -> str:
"""Append web search context to existing context
:param context: Existing context
:type context: str
:param web_search_result: Web search result
:type web_search_result: str
:return: Concatenated web search result
:rtype: str
"""
return f"{context}\nWeb Search Result: {web_search_result}"
def get_answer_from_llm(self, prompt: str):
"""
Gets an answer based on the given query and context by passing it
to an LLM.
:param prompt: Gets an answer based on the given query and context by passing it to an LLM.
:type prompt: str
:return: The answer.
:rtype: _type_
"""
return self.get_llm_model_answer(prompt)
def access_search_and_get_results(self, input_query: str):
"""
Search the internet for additional context
:param input_query: search query
:type input_query: str
:return: Search results
:rtype: Unknown
"""
from langchain.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
logging.info(f"Access search to get answers for {input_query}")
return search.run(input_query)
def _stream_query_response(self, answer: Any) -> Generator[Any, Any, None]:
"""Generator to be used as streaming response
:param answer: Answer chunk from llm
:type answer: Any
:yield: Answer chunk from llm
:rtype: Generator[Any, Any, None]
"""
streamed_answer = ""
for chunk in answer:
streamed_answer = streamed_answer + chunk
yield chunk
logging.info(f"Answer: {streamed_answer}")
def _stream_chat_response(self, answer: Any) -> Generator[Any, Any, None]:
"""Generator to be used as streaming response
:param answer: Answer chunk from llm
:type answer: Any
:yield: Answer chunk from llm
:rtype: Generator[Any, Any, None]
"""
streamed_answer = ""
for chunk in answer:
streamed_answer = streamed_answer + chunk
yield chunk
self.memory.chat_memory.add_ai_message(streamed_answer)
logging.info(f"Answer: {streamed_answer}")
def query(self, input_query: str, contexts: List[str], config: BaseLlmConfig = None, dry_run=False):
"""
Queries the vector database based on the given input query.
Gets relevant doc based on the query and then passes it to an
LLM as context to get the answer.
:param input_query: The query to use.
:type input_query: str
:param contexts: Embeddings retrieved from the database to be used as context.
:type contexts: List[str]
:param config: The `LlmConfig` instance to use as configuration options. This is used for one method call.
To persistently use a config, declare it during app init., defaults to None
:type config: Optional[BaseLlmConfig], optional
:param dry_run: A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response., defaults to False
:type dry_run: bool, optional
:return: The answer to the query or the dry run result
:rtype: str
"""
query_config = config or self.config
if self.is_docs_site_instance:
query_config.template = DOCS_SITE_PROMPT_TEMPLATE
query_config.number_documents = 5
k = {}
if self.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
prompt = self.generate_prompt(input_query, contexts, **k)
logging.info(f"Prompt: {prompt}")
if dry_run:
return prompt
answer = self.get_answer_from_llm(prompt)
if isinstance(answer, str):
logging.info(f"Answer: {answer}")
return answer
else:
return self._stream_query_response(answer)
def chat(self, input_query: str, contexts: List[str], config: BaseLlmConfig = None, dry_run=False):
"""
Queries the vector database on the given input query.
Gets relevant doc based on the query and then passes it to an
LLM as context to get the answer.
Maintains the whole conversation in memory.
:param input_query: The query to use.
:type input_query: str
:param contexts: Embeddings retrieved from the database to be used as context.
:type contexts: List[str]
:param config: The `LlmConfig` instance to use as configuration options. This is used for one method call.
To persistently use a config, declare it during app init., defaults to None
:type config: Optional[BaseLlmConfig], optional
:param dry_run: A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response., defaults to False
:type dry_run: bool, optional
:return: The answer to the query or the dry run result
:rtype: str
"""
query_config = config or self.config
if self.is_docs_site_instance:
query_config.template = DOCS_SITE_PROMPT_TEMPLATE
query_config.number_documents = 5
k = {}
if self.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
self.update_history()
prompt = self.generate_prompt(input_query, contexts, **k)
logging.info(f"Prompt: {prompt}")
if dry_run:
return prompt
answer = self.get_answer_from_llm(prompt)
self.memory.chat_memory.add_user_message(input_query)
if isinstance(answer, str):
self.memory.chat_memory.add_ai_message(answer)
logging.info(f"Answer: {answer}")
# NOTE: Adding to history before and after. This could be seen as redundant.
# If we change it, we have to change the tests (no big deal).
self.update_history()
return answer
else:
# this is a streamed response and needs to be handled differently.
return self._stream_chat_response(answer)
@staticmethod
def _get_messages(prompt: str, system_prompt: Optional[str] = None) -> List[BaseMessage]:
"""
Construct a list of langchain messages
:param prompt: User prompt
:type prompt: str
:param system_prompt: System prompt, defaults to None
:type system_prompt: Optional[str], optional
:return: List of messages
:rtype: List[BaseMessage]
"""
from langchain.schema import HumanMessage, SystemMessage
messages = []
if system_prompt:
messages.append(SystemMessage(content=system_prompt))
messages.append(HumanMessage(content=prompt))
return messages
+46
View File
@@ -0,0 +1,46 @@
from typing import Iterable, Optional, Union
from embedchain.config import BaseLlmConfig
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
@register_deserializable
class GPT4ALLLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
if self.config.model is None:
self.config.model = "orca-mini-3b.ggmlv3.q4_0.bin"
self.instance = GPT4ALLLlm._get_instance(self.config.model)
def get_llm_model_answer(self, prompt):
return self._get_gpt4all_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_instance(model):
try:
from gpt4all import GPT4All
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The GPT4All python package is not installed. Please install it with `pip install embedchain[opensource]`" # noqa E501
) from None
return GPT4All(model_name=model)
def _get_gpt4all_answer(self, prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]:
if config.model and config.model != self.config.model:
raise RuntimeError(
"OpenSourceApp does not support switching models at runtime. Please create a new app instance."
)
if config.system_prompt:
raise ValueError("OpenSourceApp does not support `system_prompt`")
response = self.instance.generate(
prompt=prompt,
streaming=config.stream,
top_p=config.top_p,
max_tokens=config.max_tokens,
temp=config.temperature,
)
return response
+26
View File
@@ -0,0 +1,26 @@
import os
from typing import Optional
from langchain.llms import Replicate
from embedchain.config import BaseLlmConfig
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
@register_deserializable
class Llama2Llm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
if "REPLICATE_API_TOKEN" not in os.environ:
raise ValueError("Please set the REPLICATE_API_TOKEN environment variable.")
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
# TODO: Move the model and other inputs into config
if self.config.system_prompt:
raise ValueError("Llama2App does not support `system_prompt`")
llm = Replicate(
model="a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
input={"temperature": self.config.temperature or 0.75, "max_length": 500, "top_p": self.config.top_p},
)
return llm(prompt)
+42
View File
@@ -0,0 +1,42 @@
from typing import Optional
import openai
from embedchain.config import BaseLlmConfig
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
@register_deserializable
class OpenAiLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
# NOTE: This class does not use langchain. One reason is that `top_p` is not supported.
def get_llm_model_answer(self, prompt):
messages = []
if self.config.system_prompt:
messages.append({"role": "system", "content": self.config.system_prompt})
messages.append({"role": "user", "content": prompt})
response = openai.ChatCompletion.create(
model=self.config.model or "gpt-3.5-turbo-0613",
messages=messages,
temperature=self.config.temperature,
max_tokens=self.config.max_tokens,
top_p=self.config.top_p,
stream=self.config.stream,
)
if self.config.stream:
return self._stream_llm_model_response(response)
else:
return response["choices"][0]["message"]["content"]
def _stream_llm_model_response(self, response):
"""
This is a generator for streaming response from the OpenAI completions API
"""
for line in response:
chunk = line["choices"][0].get("delta", {}).get("content", "")
yield chunk
+28
View File
@@ -0,0 +1,28 @@
import logging
from typing import Optional
from embedchain.config import BaseLlmConfig
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.llm.base_llm import BaseLlm
@register_deserializable
class VertexAiLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return VertexAiLlm._get_athrophic_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_athrophic_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain.chat_models import ChatVertexAI
chat = ChatVertexAI(temperature=config.temperature, model=config.model)
if config.top_p and config.top_p != 1:
logging.warning("Config option `top_p` is not supported by this model.")
messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
+12
View File
@@ -0,0 +1,12 @@
from embedchain.helper_classes.json_serializable import JSONSerializable
class BaseLoader(JSONSerializable):
def __init__(self):
pass
def load_data():
"""
Implemented by child classes
"""
pass
+46
View File
@@ -0,0 +1,46 @@
import csv
from io import StringIO
from urllib.parse import urlparse
import requests
from embedchain.loaders.base_loader import BaseLoader
class CsvLoader(BaseLoader):
@staticmethod
def _detect_delimiter(first_line):
delimiters = [",", "\t", ";", "|"]
counts = {delimiter: first_line.count(delimiter) for delimiter in delimiters}
return max(counts, key=counts.get)
@staticmethod
def _get_file_content(content):
url = urlparse(content)
if all([url.scheme, url.netloc]) and url.scheme not in ["file", "http", "https"]:
raise ValueError("Not a valid URL.")
if url.scheme in ["http", "https"]:
response = requests.get(content)
response.raise_for_status()
return StringIO(response.text)
elif url.scheme == "file":
path = url.path
return open(path, newline="") # Open the file using the path from the URI
else:
return open(content, newline="") # Treat content as a regular file path
@staticmethod
def load_data(content):
"""Load a csv file with headers. Each line is a document"""
result = []
with CsvLoader._get_file_content(content) as file:
first_line = file.readline()
delimiter = CsvLoader._detect_delimiter(first_line)
file.seek(0) # Reset the file pointer to the start
reader = csv.DictReader(file, delimiter=delimiter)
for i, row in enumerate(reader):
line = ", ".join([f"{field}: {value}" for field, value in row.items()])
result.append({"content": line, "meta_data": {"url": content, "row": i + 1}})
return result
+5 -1
View File
@@ -4,8 +4,12 @@ from urllib.parse import urljoin, urlparse
import requests
from bs4 import BeautifulSoup
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
class DocsSiteLoader:
@register_deserializable
class DocsSiteLoader(BaseLoader):
def __init__(self):
self.visited_links = set()
+5 -1
View File
@@ -1,7 +1,11 @@
from langchain.document_loaders import Docx2txtLoader
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
class DocxFileLoader:
@register_deserializable
class DocxFileLoader(BaseLoader):
def load_data(self, url):
"""Load data from a .docx file."""
loader = Docx2txtLoader(url)
+6 -1
View File
@@ -1,4 +1,9 @@
class LocalQnaPairLoader:
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
@register_deserializable
class LocalQnaPairLoader(BaseLoader):
def load_data(self, content):
"""Load data from a local QnA pair."""
question, answer = content
+6 -1
View File
@@ -1,4 +1,9 @@
class LocalTextLoader:
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
@register_deserializable
class LocalTextLoader(BaseLoader):
def load_data(self, content):
"""Load data from a local text file."""
meta_data = {
+43
View File
@@ -0,0 +1,43 @@
import logging
import os
try:
from llama_index import download_loader
except ImportError:
raise ImportError("Notion requires extra dependencies. Install with `pip install embedchain[community]`") from None
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
@register_deserializable
class NotionLoader(BaseLoader):
def load_data(self, source):
"""Load data from a PDF file."""
NotionPageReader = download_loader("NotionPageReader")
# Reformat Id to match notion expectation
id = source[-32:]
formatted_id = f"{id[:8]}-{id[8:12]}-{id[12:16]}-{id[16:20]}-{id[20:]}"
logging.debug(f"Extracted notion page id as: {formatted_id}")
# Get page through the notion api
integration_token = os.getenv("NOTION_INTEGRATION_TOKEN")
reader = NotionPageReader(integration_token=integration_token)
documents = reader.load_data(page_ids=[formatted_id])
# Extract text
raw_text = documents[0].text
# Clean text
text = clean_string(raw_text)
return [
{
"content": text,
"meta_data": {"url": f"notion-{formatted_id}"},
}
]
+4 -1
View File
@@ -1,9 +1,12 @@
from langchain.document_loaders import PyPDFLoader
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class PdfFileLoader:
@register_deserializable
class PdfFileLoader(BaseLoader):
def load_data(self, url):
"""Load data from a PDF file."""
loader = PyPDFLoader(url)
+16 -3
View File
@@ -4,10 +4,14 @@ import requests
from bs4 import BeautifulSoup
from bs4.builder import ParserRejectedMarkup
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.loaders.web_page import WebPageLoader
from embedchain.utils import is_readable
class SitemapLoader:
@register_deserializable
class SitemapLoader(BaseLoader):
def load_data(self, sitemap_url):
"""
This method takes a sitemap URL as input and retrieves
@@ -20,11 +24,20 @@ class SitemapLoader:
response.raise_for_status()
soup = BeautifulSoup(response.text, "xml")
links = [link.text for link in soup.find_all("loc")]
links = [link.text for link in soup.find_all("loc") if link.parent.name == "url"]
if len(links) == 0:
# Get all <loc> tags as a fallback. This might include images.
links = [link.text for link in soup.find_all("loc")]
for link in links:
try:
each_load_data = web_page_loader.load_data(link)
output.append(each_load_data)
if is_readable(each_load_data[0].get("content")):
output.append(each_load_data)
else:
logging.warning(f"Page is not readable (too many invalid characters): {link}")
except ParserRejectedMarkup as e:
logging.error(f"Failed to parse {link}: {e}")
return [data[0] for data in output]
+4 -1
View File
@@ -3,10 +3,13 @@ import logging
import requests
from bs4 import BeautifulSoup
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class WebPageLoader:
@register_deserializable
class WebPageLoader(BaseLoader):
def load_data(self, url):
"""Load data from a web page."""
response = requests.get(url)
+4 -1
View File
@@ -1,9 +1,12 @@
from langchain.document_loaders import YoutubeLoader
from embedchain.helper_classes.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class YoutubeVideoLoader:
@register_deserializable
class YoutubeVideoLoader(BaseLoader):
def load_data(self, url):
"""Load data from a Youtube video."""
loader = YoutubeLoader.from_youtube_url(url, add_video_info=True)
+8
View File
@@ -0,0 +1,8 @@
from enum import Enum
class EmbeddingFunctions(Enum):
OPENAI = "OPENAI"
HUGGING_FACE = "HUGGING_FACE"
VERTEX_AI = "VERTEX_AI"
GPT4ALL = "GPT4ALL"
+9
View File
@@ -0,0 +1,9 @@
from enum import Enum
class Providers(Enum):
OPENAI = "OPENAI"
ANTHROPHIC = "ANTHPROPIC"
VERTEX_AI = "VERTEX_AI"
GPT4ALL = "GPT4ALL"
AZURE_OPENAI = "AZURE_OPENAI"
+6
View File
@@ -0,0 +1,6 @@
from enum import Enum
class VectorDatabases(Enum):
CHROMADB = "CHROMADB"
ELASTICSEARCH = "ELASTICSEARCH"
+9
View File
@@ -0,0 +1,9 @@
from enum import Enum
# vector length created by embedding fn
class VectorDimensions(Enum):
GPT4ALL = 384
OPENAI = 1536
VERTEX_AI = 768
HUGGING_FACE = 384

Some files were not shown because too many files have changed in this diff Show More