Compare commits

...

114 Commits

Author SHA1 Message Date
Taranjeet Singh 39640cb697 feat: bump version to 0.0.64 (#733) 2023-09-30 12:16:22 +05:30
cachho 9ecf2e9feb feat: One App (#635)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-30 12:00:43 +05:30
Taranjeet Singh 2db07cdb1f docs: add gpt-4 as llm FAQ (#731) 2023-09-30 10:24:49 +05:30
Taranjeet Singh faa29ef285 fix: Update doc heading name (#730) 2023-09-30 10:01:57 +05:30
Prikshit 16b0d5b829 Rename method _get_gpt4all_answer to _get_answer (#727) 2023-09-30 09:45:31 +05:30
Deshraj Yadav 6ae33d04b8 [OpenSearch] Add support for filtering docs based on app_id in opensearch db (#729) 2023-09-29 15:19:10 -07:00
Deshraj Yadav 333eb8d60f Update version to 0.0.62 (#726) 2023-09-28 15:03:20 -07:00
Deshraj Yadav 414c69fd62 Add support for OpenSearch as vector database (#725) 2023-09-29 03:24:42 +05:30
Prikshit 9951b58005 Rename _get_azure_openai_answer to _get_answer (#724) 2023-09-29 00:13:49 +05:30
Osama Nabih e73ed82ae9 chore: Renamed EmbeddingFunctions.py to follow snakecase convention (#669)
Signed-off-by: Osama Nabih <nabih_the_4th@yahoo.com>
2023-09-28 11:35:10 +05:30
Tushar kalsi 8bde34a24f Fix #718 Added Slack in Mint.Js (#720) 2023-09-28 11:32:33 +05:30
Taranjeet Singh b58380e505 docs: add langsmith integration. (#717) 2023-09-28 03:16:53 +05:30
Ayush Mishra 16f8de810c Chore/follow_snake_conventions_in_config (#716) 2023-09-27 21:38:42 +05:30
Taranjeet Singh 70f2de4fd3 feat: bump version to 0.0.61 (#709) 2023-09-27 10:53:26 +05:30
Subhajit Ghosh a5d5e5825f Renamed apps/App.py to follow snake case convention (#708) 2023-09-27 10:35:22 +05:30
Taranjeet Singh 0f16c72762 fix: use openai llm via langchain (#670)
Co-authored-by: Deshraj Yadav <deshrajdry@gmail.com>
2023-09-27 10:34:02 +05:30
Subhajit Ghosh 9ca7a0d6d1 Rename models/Providers.py to follow snake case convention (#707) 2023-09-27 10:31:08 +05:30
Subhajit Ghosh 7d5bfd8c9f Renamed models/VectorDimensions.py to follow snake case convention (#706) 2023-09-27 10:15:28 +05:30
Taranjeet Singh cc9a06b116 feat: bump version to 0.0.60 (#704) 2023-09-27 10:03:31 +05:30
Subhajit Ghosh b8fc7b0c9e Renamed models/VectorDatabases.py to follow snake case convention (#703) 2023-09-27 09:56:11 +05:30
Subhajit Ghosh 3999f2a373 Renamed config/apps/OpenSourceAppConfig.py to follow snake case convention (#701) 2023-09-27 09:37:31 +05:30
Subhajit Ghosh 4eb2c0e123 Renamed apps/CustomApp.py to follow snake case convention (#678) 2023-09-27 04:59:46 +05:30
Ayush Mishra b8a838aee1 Remove load_and_embed_v1 method since it is not used (#638) 2023-09-27 04:58:49 +05:30
Kapil Mirchandani 84e5932ea5 Rename BaseAppConfig to be snake_case (#689) 2023-09-27 02:36:58 +05:30
Shubham Pal e41573ca74 Fix: Added docx2txt dependency (#682)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-27 02:00:38 +05:30
Kapil Mirchandani 4c5c99e6ae Rename CustomAppConfig to be snake_case (#680) 2023-09-27 01:24:39 +05:30
Shivam Menda dcb940ba95 Refactored AppConfig.py to app_config.py (#681) 2023-09-26 23:54:42 +05:30
Subhajit Ghosh 8d3b66f7e3 Converted OpenSourceApp.py file name to Open_Source_App.py (#675) 2023-09-25 23:17:53 -07:00
Deshraj Yadav bc89b6ea74 Bump version to 0.0.59 (#651) 2023-09-24 18:15:29 -07:00
sw8fbar f0742dffa2 Docs: where context filter (#547) 2023-09-24 13:32:27 -07:00
cachho 6c71a1020d Docs: use LlmConfig instead of QueryConfig (#626) 2023-09-24 11:48:03 -07:00
Dev Khant 1db3e43adf Add support for dry_run in load_and_embed_v2 method (#634) 2023-09-24 11:42:14 -07:00
Ayush Mishra cb59b0b5e4 Bump embedchain package version in examples (#639) 2023-09-24 09:53:06 -07:00
Dev Khant 8e0f05055e Add chat feature to discord_bot example (#643) 2023-09-24 09:49:59 -07:00
Naman Jain 4768bacf1c docs: large csv file error (#644) 2023-09-24 09:48:36 -07:00
Raghav Tyagi dc206c0999 Fixed minor grammatical issues in documentation: Interface Types (#648) 2023-09-24 09:47:47 -07:00
Dev Khant 77e1983b2e bug: app.online fixed (#647) 2023-09-23 16:10:18 -07:00
Dev Khant d344ee226c Set telemetry flag as a top level attribute (#462) 2023-09-18 10:29:14 +05:30
cachho 3d0e4141bf refactor: get existing doc id method (#616) 2023-09-17 23:22:12 +05:30
Dev Khant 01fb216ff7 allow_reset as constructor argument (#618) 2023-09-15 07:26:02 -07:00
David Talson a662b2a6c6 change app.count to app.db.count (#620) 2023-09-14 19:39:57 -07:00
Subhadip Mandal b1af82eba8 Changed Tesla url to Forbes Url (#615)
Co-authored-by: Subhadip <mnhacker2001@gmail.com>
2023-09-14 08:57:41 +05:30
Taranjeet Singh 378ef5246e feat: bump version to 0.0.58 (#617) 2023-09-14 02:20:31 +05:30
Lovepreet Singh 606814f10e Fix a typo - accpeting -> accepting (#614) 2023-09-14 02:07:06 +05:30
David Talson 5e06a0d001 Fix/dont print the entire text when data type is text (#605) 2023-09-14 02:06:23 +05:30
cachho c0e3274375 Fix/chat (#609) 2023-09-14 02:04:27 +05:30
cachho 119ec5e405 fix: elastic search (#600) 2023-09-13 23:28:18 +05:30
cachho 79efa51941 fix: url metadata for all datatypes (#613) 2023-09-13 10:19:48 -07:00
Deshraj Yadav 701d0b21ef [chore] fix lint issues (#607) 2023-09-12 20:04:31 -07:00
Taranjeet Singh 0f23d5f967 feat: bump version to 0.0.57 (#606) 2023-09-13 08:17:54 +05:30
Taranjeet Singh 36b26e08c3 feat: add support for mdx file (#604) 2023-09-13 05:13:18 +05:30
cachho ac08638a63 fix: do not mock get (#598) 2023-09-12 22:42:17 +05:30
cachho 03146946fa chore: linting (#597) 2023-09-12 21:34:38 +05:30
cachho 0f9a10c598 fix: use template from tempory LlmConfig (#590) 2023-09-12 21:33:58 +05:30
Taranjeet Singh 2bd6881361 feat: Add embedding manager (#570) 2023-09-12 12:13:53 +05:30
Taranjeet Singh ba208f5b48 feature: bump version to 0.0.56 (#595) 2023-09-12 09:52:01 +05:30
Dev Khant bdef85f7db Handle if no module found for bots (#564)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-12 09:29:35 +05:30
cachho 2cb47938fd fix: llama2 - use config with specific defaults (#594) 2023-09-12 09:28:42 +05:30
cachho dfe0b414ac refactor: use llama hub instead of llama index (#592) 2023-09-12 09:26:58 +05:30
cachho 1864f4cb38 fix: serialize non serializable (#589) 2023-09-12 09:25:46 +05:30
Dev Khant 7c39d9f0c1 Add dry_run to add() (#545) 2023-09-12 09:20:31 +05:30
Deshraj Yadav 79f5a1d052 [chore]: Rename modules for better readability and maintainability (#587) 2023-09-11 07:01:40 +05:30
Deshraj Yadav 6fed75bb45 Remove elasticsearch as mandatory dependency (#585) 2023-09-09 22:20:24 -07:00
Taranjeet Singh 352ed3b6a1 fix: update slack link (#583) 2023-09-10 04:46:18 +05:30
Taranjeet Singh b37691711c feat: add slack community (#582) 2023-09-10 04:43:49 +05:30
Dev Khant 13fda2efe1 fix: --upgrade flag for all pip instances (#557) 2023-09-08 08:12:55 +05:30
Taranjeet Singh 3c3d98b9c3 feat: add embedchain javascript package (#576) 2023-09-07 05:52:44 +05:30
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
185 changed files with 24906 additions and 2034 deletions
+4 -1
View File
@@ -1,5 +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
about: General community discussions
+8 -4
View File
@@ -19,12 +19,16 @@ jobs:
with:
python-version: '3.11'
- name: Install pep517
- name: Install Poetry
run: |
python -m pip install pep517 --user
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: python -m pep517.build .
run: poetry build
- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
+13 -2
View File
@@ -7,8 +7,19 @@ PROJECT_NAME := embedchain
.PHONY: install format lint clean test ci_lint ci_test
install:
$(PIP) install --upgrade pip
$(PIP) install -e .[dev]
poetry install
install_es:
poetry install --extras elasticsearch
install_opensearch:
poetry install --extras opensearch
shell:
poetry shell
py_shell:
poetry run python
format:
$(PYTHON) -m black .
+28 -29
View File
@@ -1,42 +1,28 @@
# embedchain
[![PyPI](https://img.shields.io/pypi/v/embedchain)](https://pypi.org/project/embedchain/)
[![Discord](https://dcbadge.vercel.app/api/server/6PzXDgEjG5?style=flat)](https://discord.gg/6PzXDgEjG5)
[![Slack](https://img.shields.io/badge/slack-embedchain-brightgreen.svg?logo=slack)](https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw)
[![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)
## Community
* Join embedchain community on slack by accepting [this invite](https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw)
## 🤝 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
```
## 🔥 Latest
- **[2023/07/19]** Released support for 🦙 `llama2` model. Start creating your `llama2` based bots like this:
```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.
```
## 🔍 Demo
Try out embedchain in your browser:
@@ -51,6 +37,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:
@@ -65,12 +61,12 @@ elon_bot = App()
# Embed online resources
elon_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
elon_bot.add("https://tesla.com/elon-musk")
elon_bot.add("https://www.forbes.com/profile/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?")
# Answer: Elon Musk runs four companies: Tesla, SpaceX, Neuralink, and The Boring Company
elon_bot.query("How many companies does Elon Musk run and name those?")
# Answer: Elon Musk currently runs several companies. As of my knowledge, he is the CEO and lead designer of SpaceX, the CEO and product architect of Tesla, Inc., the CEO and founder of Neuralink, and the CEO and founder of The Boring Company. However, please note that this information may change over time, so it's always good to verify the latest updates.
```
## 🤝 Contributing
@@ -78,8 +74,11 @@ 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 refrence, please go through [Development Guide](https://docs.embedchain.ai/contribution/dev) and [Documentation Guide](https://docs.embedchain.ai/contribution/docs).
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
+2 -3
View File
@@ -4,13 +4,12 @@ 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` 🤖
- This step assumes that you have already created an `App`. 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()
naval_chat_bot = App()
# Embed Online Resources
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
+152 -67
View File
@@ -4,96 +4,120 @@ title: '📱 App types'
## App Types
We have three types of App.
Embedchain supports a variety of LLMs, embedding functions/models and vector databases.
### App
Our app gives you full control over which components you want to use, you can mix and match them to your hearts content.
<Tip>
Out of the box, if you just use `app = App()`, Embedchain uses what we believe to be the best configuration available. This might include paid/proprietary components. Currently, this is
* LLM: OpenAi (gpt-3.5-turbo-0613)
* Embedder: OpenAi (text-embedding-ada-002)
* Database: ChromaDB
</Tip>
### LLM
#### Choosing an LLM
The following LLM providers are supported by Embedchain:
- OPENAI
- ANTHPROPIC
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
- LLAMA2
You can choose one by importing it from `embedchain.llm`. E.g.:
```python
from embedchain import App
app = App()
from embedchain.llm.llama2 import Llama2Llm
app = App(llm=Llama2Llm())
```
- `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` 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`
#### Configuration
The LLMs can be configured by passing an LlmConfig object.
The config options can be found [here](/advanced/query_configuration#llmconfig)
```python
import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
from embedchain import App
from embedchain.llm.llama2 import Llama2Llm
from embedchain.config import LlmConfig
app = App(llm=Llama2Llm(), llm_config=LlmConfig(number_documents=3, temperature=0))
```
### Llama2App
### Embedder
#### Choosing an Embedder
The following providers for embedding functions are supported by Embedchain:
- OPENAI
- HUGGING_FACE
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
You can choose one by importing it from `embedchain.embedder`. E.g.:
```python
import os
from embedchain import App
from embedchain.embedder.vertexai import VertexAiEmbedder
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.
app = App(embedder=VertexAiEmbedder())
```
- `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 have don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
#### Configuration
### OpenSourceApp
The LLMs can be configured by passing an EmbedderConfig object.
```python
from embedchain import OpenSourceApp
app = OpenSourceApp()
from embedchain import App
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.config import EmbedderConfig
app = App(embedder=OpenAiEmbedder(), embedder_config=EmbedderConfig(model="text-embedding-ada-002"))
```
- `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]`.
<Tip>
You can also pass an `LlmConfig` instance directly to the `query` or `chat` method.
This creates a temporary config for that request alone, so you could, for example, use a different model (from the same provider) or get more context documents for a specific query.
</Tip>
### CustomApp
### Vector Database
#### Choosing a Vector Database
The following vector databases are supported by Embedchain:
- ChromaDB
- Elasticsearch
You can choose one by importing it from `embedchain.vectordb`. E.g.:
```python
from embedchain import CustomApp
from embedchain.config import CustomAppConfig
from embedchain.models import Providers, EmbeddingFunctions
from embedchain import App
from embedchain.vectordb.elasticsearch import ElasticsearchDB
config = CustomAppConfig(embedding_fn=EmbeddingFunctions.OPENAI, provider=Providers.OPENAI)
app = CustomApp(config)
app = App(db=ElasticsearchDB())
```
- `CustomApp` is not opinionated.
- Configuration required. It's for advanced users who want to mix and match different embedding models and LLMs. Configuration required.
- while it's doing that, it's still providing abstractions through `Providers`.
- 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
- Following embedding functions are available for an embedding function
- OPENAI
- HUGGING_FACE
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
#### Configuration
The vector databases can be configured by passing a specific config object.
These vary greatly between the different vector databases.
```python
from embedchain import App
from embedchain.vectordb.elasticsearch import ElasticsearchDB
from embedchain.config import ElasticsearchDBConfig
app = App(db=ElasticsearchDB(), db_config=ElasticsearchDBConfig())
```
### PersonApp
@@ -103,7 +127,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
@@ -111,18 +135,79 @@ import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
#### Compatibility with other apps
### Full Configuration Examples
Embedchain previously offered fully configured classes, namely `App`, `OpenSourceApp`, `CustomApp` and `Llama2App`.
We deprecated these apps. The reason for this decision was that it was hard to switch from to a different LLM, embedder or vector db, if you one day decided that that's what you want to do.
The new app allows drop-in replacements, such as changing `App(llm=OpenAiLlm())` to `App(llm=Llama2Llm())`.
To make the switch to our new, fully configurable app easier, we provide you with full examples for what the old classes would look like implemented as a new app.
You can swap these in, and if you decide you want to try a different model one day, you don't have to rewrite your whole bot.
#### App
App without any configuration is still using the best options available, so you can keep using:
```python
from embedchain import App
app = App()
```
#### OpenSourceApp
Use this snippet to run an open source app.
```python
from embedchain import App
from embedchain.llm.gpt4all import GPT4ALLLlm
from embedchain.embedder.gpt4all import GPT4AllEmbedder
from embedchain.vectordb.chroma import ChromaDB
app = App(llm=GPT4ALLLlm(), embedder=GPT4AllEmbedder(), db=ChromaDB())
```
#### Llama2App
```python
from embedchain import App
from embedchain.llm.llama2 import Llama2Llm
app = App(llm=Llama2Llm())
```
#### CustomApp
Every app is a custom app now.
If you were previously using a `CustomApp`, you can now just change it to `App`.
Here's one example, what you could do if we combined everything shown on this page.
```python
from embedchain import App
from embedchain.config import ElasticsearchDBConfig, EmbedderConfig, LlmConfig
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.llm.llama2 import Llama2Llm
from embedchain.vectordb.elasticsearch import ElasticsearchDB
app = App(
llm=Llama2Llm(),
llm_config=LlmConfig(number_documents=3, temperature=0),
embedder=OpenAiEmbedder(),
embedder_config=EmbedderConfig(model="text-embedding-ada-002"),
db=ElasticsearchDB(),
db_config=ElasticsearchDBConfig(),
)
```
### Compatibility with other apps
- If there is any other app instance in your script or app, you can change the import as
```python
from embedchain import App as EmbedChainApp
from embedchain import OpenSourceApp as EmbedChainOSApp
from embedchain import PersonApp as EmbedChainPersonApp
# or
from embedchain import App as ECApp
from embedchain import OpenSourceApp as ECOSApp
from embedchain import PersonApp as ECPApp
```
+34 -19
View File
@@ -4,6 +4,16 @@ 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
### General
@@ -11,31 +21,31 @@ Embedchain is made to work out of the box. However, for advanced users we're als
Here's the readme example with configuration options.
```python
import os
from embedchain import App
from embedchain.config import AppConfig, AddConfig, QueryConfig, ChunkerConfig
from chromadb.utils import embedding_functions
from embedchain.config import AppConfig, AddConfig, LlmConfig, ChunkerConfig
# Example: set the log level for debugging
config = AppConfig(log_level="DEBUG")
naval_chat_bot = App(config)
# Example: specify a custom collection name
config = AppConfig(collection_name="naval_chat_bot")
naval_chat_bot = App(config)
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("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("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)
query_config = QueryConfig()
# 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))
```
@@ -44,11 +54,13 @@ 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
@@ -56,7 +68,8 @@ page = wikipedia.page("Albert Einstein")
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(template=einstein_chat_template, system_prompt="You are Albert Einstein.")
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, config=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?
+22 -3
View File
@@ -19,7 +19,7 @@ Otherwise, you will not know when, for instance, an invalid filepath is interpre
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 it's raw text instead of following links.
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
@@ -73,6 +73,17 @@ 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")
```
Note: There is a size limit allowed for csv file beyond which it can throw error. This limit is set by the LLMs. Please consider chunking large csv files into smaller csv files.
### Code documentation website loader
To add any code documentation website as a loader, use the data_type as `docs_site`. Eg:
@@ -82,7 +93,7 @@ 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 use notion you must install the extra dependencies with `pip install --upgrade 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:
@@ -93,6 +104,14 @@ app.add("my-page-cfbc134ca6464fc980d0391613959196", "notion")
app.add("https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196", "notion")
```
### Mdx file
To add any mdx file to your app, use the data_type (first argument to `.add()` method) as `mdx`. Note that this supports support mdx file present on machine, so this should be a file path. Eg:
```python
app.add('path/to/file.mdx', data_type='mdx')
```
## Local Data Types
### Text
@@ -138,4 +157,4 @@ print(naval_chat_bot.query("What unique capacity does Naval argue humans possess
## 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.
- 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.
+6 -6
View File
@@ -19,7 +19,7 @@ print(naval_chat_bot.query("What unique capacity does Naval argue humans possess
### Chat Interface
- This interface is chat interface where it remembers previous conversation. Right now it remembers 5 conversation by default. 💬
- 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.
@@ -36,18 +36,18 @@ print(naval_chat_bot.chat("what did the author say about 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).
Dry Run is an option in the `add`, `query` and `chat` methods that allows the user to display the data chunks and their constructed prompt which is not sent 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.
- To use this, instantiate a `LlmConfig` 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)
query_config = LlmConfig(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:
@@ -70,6 +70,6 @@ app.reset()
Counts the number of embeddings (chunks) in the database.
```python
print(app.count())
print(app.db.count())
# returns: 481
```
```
+3 -1
View File
@@ -53,7 +53,7 @@ Default values of chunker config parameters for different `data_type`:
_coming soon_
## QueryConfig
## LlmConfig
|option|description|type|default|
|---|---|---|---|
@@ -67,6 +67,8 @@ _coming soon_
|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|
|where|filter for context search.|dict|None|
## ChatConfig
+28
View File
@@ -8,6 +8,7 @@ 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/))
@@ -38,6 +39,9 @@ Embedchain community has been super active in creating demos on top of Embedchai
- [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
@@ -50,6 +54,7 @@ Embedchain community has been super active in creating demos on top of Embedchai
- [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
@@ -65,6 +70,7 @@ Embedchain community has been super active in creating demos on top of Embedchai
## 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
@@ -79,9 +85,31 @@ Embedchain community has been super active in creating demos on top of Embedchai
- [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)
+16 -5
View File
@@ -6,11 +6,9 @@ title: '🧪 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.
Before you consume valueable tokens, you should make sure that data chunks are properly created and 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` option in your `query` or `chat` method.
Following the example above, add this to your script:
- For `query` or `chat` method, you can add this to your script:
```python
print(naval_chat_bot.query('Can you tell me who Naval Ravikant is?', dry_run=True))
@@ -26,4 +24,17 @@ A: Naval Ravikant is an Indian-American entrepreneur and investor.
_The embedding is confirmed to work as expected. It returns the right document, even if the question is asked slightly different. No prompt tokens have been consumed._
**The dry run will still consume tokens to embed your query, but it is only ~1/15 of the prompt.**
The dry run will still consume tokens to embed your query, but it is only **~1/15 of the prompt.**
- For `add` method, you can add this to your script:
```python
print(naval_chat_bot.add('https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf', dry_run=True))
'''
{'chunks': ['THE ALMANACK OF NAVAL RAVIKANT', 'GETTING RICH IS NOT JUST ABOUT LUCK;', 'HAPPINESS IS NOT JUST A TRAIT WE ARE'], 'metadata': [{'source': 'C:\\Users\\Dev\\AppData\\Local\\Temp\\tmp3g5mjoiz\\tmp.pdf', 'page': 0, 'url': 'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf', 'data_type': 'pdf_file'}, {'source': 'C:\\Users\\Dev\\AppData\\Local\\Temp\\tmp3g5mjoiz\\tmp.pdf', 'page': 2, 'url': 'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf', 'data_type': 'pdf_file'}, {'source': 'C:\\Users\\Dev\\AppData\\Local\\Temp\\tmp3g5mjoiz\\tmp.pdf', 'page': 2, 'url': 'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf', 'data_type': 'pdf_file'}], 'count': 7358, 'type': <DataType.PDF_FILE: 'pdf_file'>}
# less items to show for readability
'''
```
+104 -20
View File
@@ -2,33 +2,117 @@
title: '💾 Vector Database'
---
We support `Chroma` and `Elasticsearch` as two vector database.
We support `Chroma`, `Elasticsearch` and `OpenSearch` as vector databases.
`Chroma` is used as a default database.
### Elasticsearch
In order to use `Elasticsearch` as vector database we need to use App type `CustomApp`.
## Elasticsearch
### Minimal Example
In order to use `Elasticsearch` as vector database we need to use App type `CustomApp`.
1. Set the environment variables in a `.env` file.
```
OPENAI_API_KEY=sk-SECRETKEY
ELASTICSEARCH_API_KEY=SECRETKEY==
ELASTICSEARCH_URL=https://secret-domain.europe-west3.gcp.cloud.es.io:443
```
Please note that the key needs certain privileges. For testing you can just toggle off `restrict privileges` under `/app/management/security/api_keys/` in your web interface.
2. Load the app
```python
from embedchain import CustomApp
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.elasticsearch import ElasticsearchDB
es_app = CustomApp(
llm=OpenAILlm(),
embedder=OpenAIEmbedder(),
db=ElasticsearchDB(),
)
```
### More custom settings
You can get a URL for elasticsearch in the cloud, or run it locally.
The following example shows you how to configure embedchain to work with a locally running elasticsearch.
Instead of using an API key, we use http login credentials. The localhost url can be defined in .env or in the config.
```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'
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.elasticsearch import ElasticsearchDB
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")
# elasticsearch url or list of nodes url with different hosts and ports.
es_url='https://localhost:9200',
# pass named parameters supported by Python Elasticsearch client
http_auth=("elastic", "secret"),
ca_certs="~/binaries/elasticsearch-8.7.0/config/certs/http_ca.crt" # your cert path
# verify_certs=False # Alternative, if you aren't using certs
) # pass named parameters supported by elasticsearch-py
es_app = CustomApp(
config=CustomAppConfig(log_level="INFO"),
llm=OpenAILlm(),
embedder=OpenAIEmbedder(),
db=ElasticsearchDB(config=es_config),
)
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 paramaters supported by Python Elasticsearch client.
3. This should log your connection details to the console.
4. Alternatively to a URL, you `ElasticsearchDBConfig` accepts `es_url` as a list of nodes url with different hosts and ports.
5. Additionally we can pass named parameters supported by Python Elasticsearch client.
## OpenSearch 🔍
To use OpenSearch as a vector database with a CustomApp, follow these simple steps:
1. Set the `OPENAI_API_KEY` environment variable:
```
OPENAI_API_KEY=sk-xxxx
```
2. Define the OpenSearch configuration in your Python code:
```python
from embedchain import CustomApp
from embedchain.config import OpenSearchDBConfig
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.opensearch import OpenSearchDB
opensearch_url = "https://localhost:9200"
http_auth = ("username", "password")
db_config = OpenSearchDBConfig(
opensearch_url=opensearch_url,
http_auth=http_auth,
collection_name="embedchain-app",
use_ssl=True,
timeout=30,
)
db = OpenSearchDB(config=db_config)
```
2. Instantiate the app and add data:
```python
app = CustomApp(llm=OpenAILlm(), embedder=OpenAIEmbedder(), db=db)
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
app.add("https://www.forbes.com/profile/elon-musk")
app.add("https://www.britannica.com/biography/Elon-Musk")
```
3. You're all set! Start querying using the following command:
```python
app.query("What is the net worth of Elon Musk?")
```
+27 -13
View File
@@ -8,40 +8,54 @@ title: '🤖 Discord Bot'
- 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 variables.env file.
- 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
Read Messages/View Channel (under General Permissions)
Send Messages (under Text Permissions)
Read Message History (under Text Permissions)
Mention everyone (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.
- 🎉 The bot has been successfully added to your server. But it's still offline.
### 🐳 Docker Setup
### Take the bot online
1. Install embedchain python package:
- To setup your discord bot using docker, run the following command inside this folder using your terminal.
```bash
docker-compose up --build
pip install --upgrade "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
```
📝 Note: The build command might take a while to install all the packages depending on your system resources.
### 🚀 Usage Instructions
- Go to the server where you have added your bot.
- You can add data sources to the bot using the command:
- You can add data sources to the bot using the slash command:
```text
/ec add <data_type> <url_or_text>
/add <data_type> <url_or_text>
```
- You can ask your queries from the bot using the command:
- You can ask your queries from the bot using the slash command:
```text
/ec query <question>
/query <question>
```
- You can chat with the bot using the slash command:
```text
/chat <question>
```
📝 Note: To use the bot privately, you can message the bot directly by right clicking the bot and selecting `Message`.
+59
View File
@@ -0,0 +1,59 @@
---
title: '🔮 Poe Bot'
---
### 🚀 Getting started
1. Install embedchain python package:
```bash
pip install --upgrade "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.
+12 -14
View File
@@ -2,27 +2,25 @@
title: '💼 Slack Bot'
---
### 🖼️ Template Setup
### 🖼️ Setup
- Fork [this](https://replit.com/@taranjeetio/EC-Slack-Bot-Template?v=1#README.md) replit template.
- Set your `OPENAI_API_KEY` in Secrets.
- Create a workspace on Slack if you don't have one already by clicking [here](https://slack.com/intl/en-in/).
- Create a new App on your Slack account by going [here](https://api.slack.com/apps).
- Select `From Scratch`, then enter the Bot Name and select your workspace.
- On the `Basic Information` page copy the `Signing Secret` and set it in your secrets as `SLACK_SIGNING_SECRET`.
- On the left Sidebar, go to `OAuth and Permissions` and add the following scopes under `Bot Token Scopes`:
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
```
- 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`.
- Start your replit container now by clicking on `Run`.
- On the Slack API website go to `Event Subscriptions` on the left Sidebar and turn on `Enable Events`.
- Copy the generated server URL in replit, append `/chat` at its end and paste it in `Request URL` box.
- After it gets verified, click on `Subscribe to bot events`, add `message.channels` Bot User Event and click on `Save Changes`.
- Now go to your workspace, click on the bot name in the Sidebar and then add the bot to any channel you want.
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
+1 -1
View File
@@ -7,7 +7,7 @@ title: '💬 WhatsApp Bot'
1. Install embedchain python package:
```bash
pip install embedchain
pip install --upgrade embedchain
```
2. Launch your WhatsApp bot:
+16
View File
@@ -0,0 +1,16 @@
---
title: ❓ Frequently Asked Questions
description: 'Collections of all the frequently asked questions about Embedchain'
---
## How to use GPT-4 as the LLM model
```python
from embedchain import App
from embedchain.config import LlmConfig
app = App()
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
app.query("How many companies does Elon Musk run and name those?", config=LlmConfig(model="gpt-4))
```
@@ -20,12 +20,16 @@ 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")
naval_chat_bot.add("The Meanings of Life", 'text', metadata={'chapter': 'philosphy'})
# Embed Local Resources
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.
# with where context filter
naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", where={'chapter': 'philosophy'})
```
## 🚀 How it works?
@@ -6,7 +6,7 @@ description: '💡 Start building LLM powered bots under 30 seconds'
Install embedchain python package:
```bash
pip install embedchain
pip install --upgrade embedchain
```
Creating a chatbot involves 3 steps:
@@ -27,9 +27,9 @@ elon_musk_bot = App()
# Embed Online Resources
elon_musk_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
elon_musk_bot.add("https://www.tesla.com/elon-musk")
elon_musk_bot.add("https://www.forbes.com/profile/elon-musk")
response = elon_musk_bot.query("How many companies does Elon Musk run?")
response = elon_musk_bot.query("How many companies does Elon Musk run and name those?")
print(response)
# Answer: 'Elon Musk runs four companies: Tesla, SpaceX, Neuralink, and The Boring Company.'
# Answer: 'Elon Musk currently runs several companies. As of my knowledge, he is the CEO and lead designer of SpaceX, the CEO and product architect of Tesla, Inc., the CEO and founder of Neuralink, and the CEO and founder of The Boring Company. However, please note that this information may change over time, so it's always good to verify the latest updates.'
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

+51
View File
@@ -0,0 +1,51 @@
---
title: '🛠️ LangSmith'
description: 'Integrate with Langsmith to debug and monitor your LLM app'
---
Embedchain now supports integration with [LangSmith](https://www.langchain.com/langsmith).
To use langsmith, you need to do the following steps
1. Have an account on langsmith and keep the environment variables in handy
2. Set the environments variables in your app so that embedchain has context about it.
3. Just use embedchain and everything will be logged to LangSmith, so that you can better test and monitor your application.
Lets cover each step in detail.
* First make sure that you a LangSmith account created and have all the necessary variables handy. LangSmith has a [good documentation](https://docs.smith.langchain.com/) on how to get started with their service.
* Once you have the account setup, we will need the following environment variables
```bash
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
export LANGCHAIN_API_KEY=<your-api-key>
export LANGCHAIN_PROJECT=<your-project> # if not specified, defaults to "default"
```
If you are using Python, you can use the following code to set environment variables
```python
import os
os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'
os.environ['LANGCHAIN_API_KEY'] = <your-api-key>
os.environ['LANGCHAIN_PROJECT] = <your-project>
```
* Now create an app using embedchain and everything will be automatically visible in the LangSmith
```python
from embedchain import App
app = App()
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
app.query("How many companies did Elon found?")
```
* Now the entire log for this will be visible in langsmith.
<img src="/images/langsmith.png"/>
+14 -4
View File
@@ -19,6 +19,10 @@
{
"name": "Discord",
"url": "https://discord.gg/6PzXDgEjG5"
},
{
"name":"Slack",
"url":"https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw"
}
],
"topbarCtaButton": {
@@ -27,8 +31,8 @@
},
"navigation": [
{
"group": "Getting started",
"pages": ["quickstart", "introduction"]
"group": "Get started",
"pages": ["get-start/quickstart", "get-start/introduction", "get-start/faq"]
},
{
"group": "Advanced",
@@ -36,7 +40,11 @@
},
{
"group": "Examples",
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot", "examples/slack_bot", "examples/telegram_bot", "examples/whatsapp_bot"]
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot", "examples/slack_bot", "examples/telegram_bot", "examples/whatsapp_bot", "examples/poe_bot"]
},
{
"group": "Integration",
"pages": ["integration/langsmith"]
},
{
"group": "Contribution Guidelines",
@@ -47,7 +55,9 @@
"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",
"slack":"https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw"
},
"backgroundImage": "/background.png",
"isWhiteLabeled": true
+2
View File
@@ -0,0 +1,2 @@
node_modules
dist
+56
View File
@@ -0,0 +1,56 @@
{
// Configuration for JavaScript files
"extends": [
"airbnb-base",
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"endOfLine": "auto"
}
]
},
"overrides": [
// Configuration for TypeScript files
{
"files": ["**/*.ts", "**/__tests__/*.test.ts"],
"plugins": [
"@typescript-eslint",
"unused-imports",
"simple-import-sort"
],
"extends": [
"airbnb-typescript",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"endOfLine": "auto"
}
],
"@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier
"@typescript-eslint/consistent-type-imports": "error", // Ensure `import type` is used when it's necessary
"import/prefer-default-export": "off", // Named export is easier to refactor automatically
"simple-import-sort/imports": "error", // Import configuration for `eslint-plugin-simple-import-sort`
"simple-import-sort/exports": "error", // Export configuration for `eslint-plugin-simple-import-sort`
"@typescript-eslint/no-unused-vars": "off",
"react/jsx-filename-extension": "off", // Gives error
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
]
}
}
]
}
+47
View File
@@ -0,0 +1,47 @@
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v3
with:
name: dist
path: dist
- uses: actions/upload-artifact@v3
with:
name: types
path: types
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- uses: actions/download-artifact@v3
with:
name: dist
path: dist
- uses: actions/download-artifact@v3
with:
name: types
path: types
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
+138
View File
@@ -0,0 +1,138 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.ideas.md
.todos.md
# Custom
dist
types
build
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit $1
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Disable concurent to run `check-types` after ESLint in lint-staged
npx lint-staged --concurrent false
+8
View File
@@ -0,0 +1,8 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Singh"
given-names: "Taranjeet"
title: "Embedchain"
date-released: 2023-06-25
url: "https://github.com/embedchain/embedchainjs"
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+263
View File
@@ -0,0 +1,263 @@
# embedchainjs
[![Discord](https://dcbadge.vercel.app/api/server/CUU9FPhRNt?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/)
embedchain is a framework to easily create LLM powered bots over any dataset. embedchainjs is Javascript version of embedchain. If you want a python version, check out [embedchain-python](https://github.com/embedchain/embedchain)
# 🤝 Let's Talk Embedchain!
Schedule a [Feedback Session](https://cal.com/taranjeetio/ec) with Taranjeet, the founder, to discuss any issues, provide feedback, or explore improvements.
# How it works
It abstracts the entire process of loading dataset, chunking it, creating embeddings and then storing in vector database.
You can add a single or multiple dataset using `.add` and `.addLocal` function and then use `.query` function to find an answer from the added datasets.
If you want to create a Naval Ravikant bot which has 2 of his blog posts, as well as a question and answer pair you supply, all you need to do is add the links to the blog posts and the QnA pair and embedchain will create a bot for you.
```javascript
const dotenv = require("dotenv");
dotenv.config();
const { App } = require("embedchain");
//Run the app commands inside an async function only
async function testApp() {
const navalChatBot = await App();
// Embed Online Resources
await navalChatBot.add("web_page", "https://nav.al/feedback");
await navalChatBot.add("web_page", "https://nav.al/agi");
await navalChatBot.add(
"pdf_file",
"https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf"
);
// Embed Local Resources
await navalChatBot.addLocal("qna_pair", [
"Who is Naval Ravikant?",
"Naval Ravikant is an Indian-American entrepreneur and investor.",
]);
const result = await navalChatBot.query(
"What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"
);
console.log(result);
// answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
}
testApp();
```
# Getting Started
## Installation
- First make sure that you have the package installed. If not, then install it using `npm`
```bash
npm install embedchain && npm install -S openai@^3.3.0
```
- Currently, it is only compatible with openai 3.X, not the latest version 4.X. Please make sure to use the right version, otherwise you will see the `ChromaDB` error `TypeError: OpenAIApi.Configuration is not a constructor`
- Make sure that dotenv package is installed and your `OPENAI_API_KEY` in a file called `.env` in the root folder. You can install dotenv by
```js
npm install dotenv
```
- Download and install Docker on your device by visiting [this link](https://www.docker.com/). You will need this to run Chroma vector database on your machine.
- Run the following commands to setup Chroma container in Docker
```bash
git clone https://github.com/chroma-core/chroma.git
cd chroma
docker-compose up -d --build
```
- Once Chroma container has been set up, run it inside Docker
## Usage
- We use 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 dont 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`
```js
// Set this inside your .env file
OPENAI_API_KEY = "sk-xxxx";
```
- Load the environment variables inside your .js file using the following commands
```js
const dotenv = require("dotenv");
dotenv.config();
```
- Next import the `App` class from embedchain and use `.add` function to add any dataset.
- Now your app is created. You can use `.query` function to get the answer for any query.
```js
const dotenv = require("dotenv");
dotenv.config();
const { App } = require("embedchain");
async function testApp() {
const navalChatBot = await App();
// Embed Online Resources
await navalChatBot.add("web_page", "https://nav.al/feedback");
await navalChatBot.add("web_page", "https://nav.al/agi");
await navalChatBot.add(
"pdf_file",
"https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf"
);
// Embed Local Resources
await navalChatBot.addLocal("qna_pair", [
"Who is Naval Ravikant?",
"Naval Ravikant is an Indian-American entrepreneur and investor.",
]);
const result = await navalChatBot.query(
"What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"
);
console.log(result);
// answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
}
testApp();
```
- If there is any other app instance in your script or app, you can change the import as
```javascript
const { App: EmbedChainApp } = require("embedchain");
// or
const { App: ECApp } = require("embedchain");
```
## Format supported
We support the following formats:
### PDF File
To add any pdf file, use the data_type as `pdf_file`. Eg:
```javascript
await app.add("pdf_file", "a_valid_url_where_pdf_file_can_be_accessed");
```
### Web Page
To add any web page, use the data_type as `web_page`. Eg:
```javascript
await app.add("web_page", "a_valid_web_page_url");
```
### QnA Pair
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```javascript
await app.addLocal("qna_pair", ["Question", "Answer"]);
```
### More Formats coming soon
- If you want to add any other format, please create an [issue](https://github.com/embedchain/embedchainjs/issues) and we will add it to the list of supported formats.
## Testing
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 `dryRun` method.
Following the example above, add this to your script:
```js
let result = await naval_chat_bot.dryRun("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?");console.log(result);
'''
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.
terms of the unseen. And I think that’s critical. That is what humans do uniquely that no other creature, no other computer, no other intelligence—biological or artificial—that we have ever encountered does. And not only do we do it uniquely, but if we were to meet an alien species that also had the power to generate these good explanations, there is no explanation that they could generate that we could not understand. We are maximally capable of understanding. There is no concept out there that is possible in this physical reality that a human being, given sufficient time and resources and
Query: What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?
Helpful Answer:
'''
```
_The embedding is confirmed to work as expected. It returns the right document, even if the question is asked slightly different. No prompt tokens have been consumed._
**The dry run will still consume tokens to embed your query, but it is only ~1/15 of the prompt.**
# How does it work?
Creating a chat bot over any dataset needs the following steps to happen
- load the data
- create meaningful chunks
- create embeddings for each chunk
- store the chunks in vector database
Whenever a user asks any query, following process happens to find the answer for the query
- create the embedding for query
- find similar documents for this query from vector database
- pass similar documents as context to LLM to get the final answer.
The process of loading the dataset and then querying involves multiple steps and each steps has nuances of it is own.
- How should I chunk the data? What is a meaningful chunk size?
- How should I create embeddings for each chunk? Which embedding model should I use?
- How should I store the chunks in vector database? Which vector database should I use?
- Should I store meta data along with the embeddings?
- How should I find similar documents for a query? Which ranking model should I use?
These questions may be trivial for some but for a lot of us, it needs research, experimentation and time to find out the accurate answers.
embedchain is a framework which takes care of all these nuances and provides a simple interface to create bots over any dataset.
In the first release, we are making it easier for anyone to get a chatbot over any dataset up and running in less than a minute. All you need to do is create an app instance, add the data sets using `.add` function and then use `.query` function to get the relevant answer.
# Tech Stack
embedchain is built on the following stack:
- [Langchain](https://github.com/hwchase17/langchain) as an LLM framework to load, chunk and index data
- [OpenAI's Ada embedding model](https://platform.openai.com/docs/guides/embeddings) to create embeddings
- [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/gpt/chat-completions-api) as LLM to get answers given the context
- [Chroma](https://github.com/chroma-core/chroma) as the vector database to store embeddings
# Team
## Author
- Taranjeet Singh ([@taranjeetio](https://twitter.com/taranjeetio))
## Maintainer
- [cachho](https://github.com/cachho)
- [sahilyadav902](https://github.com/sahilyadav902)
## Citation
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},
year = {2023},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/embedchain/embedchainjs}},
}
```
+1
View File
@@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
@@ -0,0 +1,66 @@
import { EmbedChainApp } from '../embedchain';
const mockAdd = jest.fn();
const mockAddLocal = jest.fn();
const mockQuery = jest.fn();
jest.mock('../embedchain', () => {
return {
EmbedChainApp: jest.fn().mockImplementation(() => {
return {
add: mockAdd,
addLocal: mockAddLocal,
query: mockQuery,
};
}),
};
});
describe('Test App', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('tests the App', async () => {
mockQuery.mockResolvedValue(
'Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.'
);
const navalChatBot = await new EmbedChainApp(undefined, false);
// Embed Online Resources
await navalChatBot.add('web_page', 'https://nav.al/feedback');
await navalChatBot.add('web_page', 'https://nav.al/agi');
await navalChatBot.add(
'pdf_file',
'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf'
);
// Embed Local Resources
await navalChatBot.addLocal('qna_pair', [
'Who is Naval Ravikant?',
'Naval Ravikant is an Indian-American entrepreneur and investor.',
]);
const result = await navalChatBot.query(
'What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?'
);
expect(mockAdd).toHaveBeenCalledWith('web_page', 'https://nav.al/feedback');
expect(mockAdd).toHaveBeenCalledWith('web_page', 'https://nav.al/agi');
expect(mockAdd).toHaveBeenCalledWith(
'pdf_file',
'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf'
);
expect(mockAddLocal).toHaveBeenCalledWith('qna_pair', [
'Who is Naval Ravikant?',
'Naval Ravikant is an Indian-American entrepreneur and investor.',
]);
expect(mockQuery).toHaveBeenCalledWith(
'What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?'
);
expect(result).toBe(
'Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.'
);
});
});
@@ -0,0 +1,44 @@
import { createHash } from 'crypto';
import type { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import type { BaseLoader } from '../loaders';
import type { Input, LoaderResult } from '../models';
import type { ChunkResult } from '../models/ChunkResult';
class BaseChunker {
textSplitter: RecursiveCharacterTextSplitter;
constructor(textSplitter: RecursiveCharacterTextSplitter) {
this.textSplitter = textSplitter;
}
async createChunks(loader: BaseLoader, url: Input): Promise<ChunkResult> {
const documents: ChunkResult['documents'] = [];
const ids: ChunkResult['ids'] = [];
const datas: LoaderResult = await loader.loadData(url);
const metadatas: ChunkResult['metadatas'] = [];
const dataPromises = datas.map(async (data) => {
const { content, metaData } = data;
const chunks: string[] = await this.textSplitter.splitText(content);
chunks.forEach((chunk) => {
const chunkId = createHash('sha256')
.update(chunk + metaData.url)
.digest('hex');
ids.push(chunkId);
documents.push(chunk);
metadatas.push(metaData);
});
});
await Promise.all(dataPromises);
return {
documents,
ids,
metadatas,
};
}
}
export { BaseChunker };
@@ -0,0 +1,26 @@
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { BaseChunker } from './BaseChunker';
interface TextSplitterChunkParams {
chunkSize: number;
chunkOverlap: number;
keepSeparator: boolean;
}
const TEXT_SPLITTER_CHUNK_PARAMS: TextSplitterChunkParams = {
chunkSize: 1000,
chunkOverlap: 0,
keepSeparator: false,
};
class PdfFileChunker extends BaseChunker {
constructor() {
const textSplitter = new RecursiveCharacterTextSplitter(
TEXT_SPLITTER_CHUNK_PARAMS
);
super(textSplitter);
}
}
export { PdfFileChunker };
@@ -0,0 +1,26 @@
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { BaseChunker } from './BaseChunker';
interface TextSplitterChunkParams {
chunkSize: number;
chunkOverlap: number;
keepSeparator: boolean;
}
const TEXT_SPLITTER_CHUNK_PARAMS: TextSplitterChunkParams = {
chunkSize: 300,
chunkOverlap: 0,
keepSeparator: false,
};
class QnaPairChunker extends BaseChunker {
constructor() {
const textSplitter = new RecursiveCharacterTextSplitter(
TEXT_SPLITTER_CHUNK_PARAMS
);
super(textSplitter);
}
}
export { QnaPairChunker };
@@ -0,0 +1,26 @@
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { BaseChunker } from './BaseChunker';
interface TextSplitterChunkParams {
chunkSize: number;
chunkOverlap: number;
keepSeparator: boolean;
}
const TEXT_SPLITTER_CHUNK_PARAMS: TextSplitterChunkParams = {
chunkSize: 500,
chunkOverlap: 0,
keepSeparator: false,
};
class WebPageChunker extends BaseChunker {
constructor() {
const textSplitter = new RecursiveCharacterTextSplitter(
TEXT_SPLITTER_CHUNK_PARAMS
);
super(textSplitter);
}
}
export { WebPageChunker };
@@ -0,0 +1,6 @@
import { BaseChunker } from './BaseChunker';
import { PdfFileChunker } from './PdfFile';
import { QnaPairChunker } from './QnaPair';
import { WebPageChunker } from './WebPage';
export { BaseChunker, PdfFileChunker, QnaPairChunker, WebPageChunker };
+317
View File
@@ -0,0 +1,317 @@
/* eslint-disable max-classes-per-file */
import type { Collection } from 'chromadb';
import type { QueryResponse } from 'chromadb/dist/main/types';
import * as fs from 'fs';
import { Document } from 'langchain/document';
import OpenAI from 'openai';
import * as path from 'path';
import { v4 as uuidv4 } from 'uuid';
import type { BaseChunker } from './chunkers';
import { PdfFileChunker, QnaPairChunker, WebPageChunker } from './chunkers';
import type { BaseLoader } from './loaders';
import { LocalQnaPairLoader, PdfFileLoader, WebPageLoader } from './loaders';
import type {
DataDict,
DataType,
FormattedResult,
Input,
LocalInput,
Metadata,
Method,
RemoteInput,
} from './models';
import { ChromaDB } from './vectordb';
import type { BaseVectorDB } from './vectordb/BaseVectorDb';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
class EmbedChain {
dbClient: any;
// TODO: Definitely assign
collection!: Collection;
userAsks: [DataType, Input][] = [];
initApp: Promise<void>;
collectMetrics: boolean;
sId: string; // sessionId
constructor(db?: BaseVectorDB, collectMetrics: boolean = true) {
if (!db) {
this.initApp = this.setupChroma();
} else {
this.initApp = this.setupOther(db);
}
this.collectMetrics = collectMetrics;
// Send anonymous telemetry
this.sId = uuidv4();
this.sendTelemetryEvent('init');
}
async setupChroma(): Promise<void> {
const db = new ChromaDB();
await db.initDb;
this.dbClient = db.client;
if (db.collection) {
this.collection = db.collection;
} else {
// TODO: Add proper error handling
console.error('No collection');
}
}
async setupOther(db: BaseVectorDB): Promise<void> {
await db.initDb;
// TODO: Figure out how we can initialize an unknown database.
// this.dbClient = db.client;
// this.collection = db.collection;
this.userAsks = [];
}
static getLoader(dataType: DataType) {
const loaders: { [t in DataType]: BaseLoader } = {
pdf_file: new PdfFileLoader(),
web_page: new WebPageLoader(),
qna_pair: new LocalQnaPairLoader(),
};
return loaders[dataType];
}
static getChunker(dataType: DataType) {
const chunkers: { [t in DataType]: BaseChunker } = {
pdf_file: new PdfFileChunker(),
web_page: new WebPageChunker(),
qna_pair: new QnaPairChunker(),
};
return chunkers[dataType];
}
public async add(dataType: DataType, url: RemoteInput) {
const loader = EmbedChain.getLoader(dataType);
const chunker = EmbedChain.getChunker(dataType);
this.userAsks.push([dataType, url]);
const { documents, countNewChunks } = await this.loadAndEmbed(
loader,
chunker,
url
);
if (this.collectMetrics) {
const wordCount = documents.reduce(
(sum, document) => sum + document.split(' ').length,
0
);
this.sendTelemetryEvent('add', {
data_type: dataType,
word_count: wordCount,
chunks_count: countNewChunks,
});
}
}
public async addLocal(dataType: DataType, content: LocalInput) {
const loader = EmbedChain.getLoader(dataType);
const chunker = EmbedChain.getChunker(dataType);
this.userAsks.push([dataType, content]);
const { documents, countNewChunks } = await this.loadAndEmbed(
loader,
chunker,
content
);
if (this.collectMetrics) {
const wordCount = documents.reduce(
(sum, document) => sum + document.split(' ').length,
0
);
this.sendTelemetryEvent('add_local', {
data_type: dataType,
word_count: wordCount,
chunks_count: countNewChunks,
});
}
}
protected async loadAndEmbed(
loader: any,
chunker: BaseChunker,
src: Input
): Promise<{
documents: string[];
metadatas: Metadata[];
ids: string[];
countNewChunks: number;
}> {
const embeddingsData = await chunker.createChunks(loader, src);
let { documents, ids, metadatas } = embeddingsData;
const existingDocs = await this.collection.get({ ids });
const existingIds = new Set(existingDocs.ids);
if (existingIds.size > 0) {
const dataDict: DataDict = {};
for (let i = 0; i < ids.length; i += 1) {
const id = ids[i];
if (!existingIds.has(id)) {
dataDict[id] = { doc: documents[i], meta: metadatas[i] };
}
}
if (Object.keys(dataDict).length === 0) {
console.log(`All data from ${src} already exists in the database.`);
return { documents: [], metadatas: [], ids: [], countNewChunks: 0 };
}
ids = Object.keys(dataDict);
const dataValues = Object.values(dataDict);
documents = dataValues.map(({ doc }) => doc);
metadatas = dataValues.map(({ meta }) => meta);
}
const countBeforeAddition = await this.count();
await this.collection.add({ documents, metadatas, ids });
const countNewChunks = (await this.count()) - countBeforeAddition;
console.log(
`Successfully saved ${src}. New chunks count: ${countNewChunks}`
);
return { documents, metadatas, ids, countNewChunks };
}
static async formatResult(
results: QueryResponse
): Promise<FormattedResult[]> {
return results.documents[0].map((document: any, index: number) => {
const metadata = results.metadatas[0][index] || {};
// TODO: Add proper error handling
const distance = results.distances ? results.distances[0][index] : null;
return [new Document({ pageContent: document, metadata }), distance];
});
}
static async getOpenAiAnswer(prompt: string) {
const messages: OpenAI.Chat.CreateChatCompletionRequestMessage[] = [
{ role: 'user', content: prompt },
];
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages,
temperature: 0,
max_tokens: 1000,
top_p: 1,
});
return (
response.choices[0].message?.content ?? 'Response could not be processed.'
);
}
protected async retrieveFromDatabase(inputQuery: string) {
const result = await this.collection.query({
nResults: 1,
queryTexts: [inputQuery],
});
const resultFormatted = await EmbedChain.formatResult(result);
const content = resultFormatted[0][0].pageContent;
return content;
}
static generatePrompt(inputQuery: string, context: any) {
const 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.\n${context}\nQuery: ${inputQuery}\nHelpful Answer:`;
return prompt;
}
static async getAnswerFromLlm(prompt: string) {
const answer = await EmbedChain.getOpenAiAnswer(prompt);
return answer;
}
public async query(inputQuery: string) {
const context = await this.retrieveFromDatabase(inputQuery);
const prompt = EmbedChain.generatePrompt(inputQuery, context);
const answer = await EmbedChain.getAnswerFromLlm(prompt);
this.sendTelemetryEvent('query');
return answer;
}
public async dryRun(input_query: string) {
const context = await this.retrieveFromDatabase(input_query);
const prompt = EmbedChain.generatePrompt(input_query, context);
return prompt;
}
/**
* Count the number of embeddings.
* @returns {Promise<number>}: The number of embeddings.
*/
public count(): Promise<number> {
return this.collection.count();
}
protected async sendTelemetryEvent(method: Method, extraMetadata?: object) {
if (!this.collectMetrics) {
return;
}
const url = 'https://api.embedchain.ai/api/v1/telemetry/';
// Read package version from filesystem (because it's not in the ts root dir)
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const metadata = {
s_id: this.sId,
version: packageJson.version,
method,
language: 'js',
...extraMetadata,
};
const maxRetries = 3;
// Retry the fetch
for (let i = 0; i < maxRetries; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({ metadata }),
});
if (response.ok) {
// Break out of the loop if the request was successful
break;
} else {
// Log the unsuccessful response (optional)
console.error(
`Telemetry: Attempt ${i + 1} failed with status:`,
response.status
);
}
} catch (error) {
// Log the error (optional)
console.error(`Telemetry: Attempt ${i + 1} failed with error:`, error);
}
// If this was the last attempt, throw an error or handle the failure
if (i === maxRetries - 1) {
console.error('Telemetry: Max retries reached');
}
}
}
}
class EmbedChainApp extends EmbedChain {
// The EmbedChain app.
// Has two functions: add and query.
// adds(dataType, 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.
}
export { EmbedChainApp };
+7
View File
@@ -0,0 +1,7 @@
import { EmbedChainApp } from './embedchain';
export const App = async () => {
const app = new EmbedChainApp();
await app.initApp;
return app;
};
@@ -0,0 +1,5 @@
import type { Input, LoaderResult } from '../models';
export abstract class BaseLoader {
abstract loadData(src: Input): Promise<LoaderResult>;
}
@@ -0,0 +1,21 @@
import type { LoaderResult, QnaPair } from '../models';
import { BaseLoader } from './BaseLoader';
class LocalQnaPairLoader extends BaseLoader {
// eslint-disable-next-line class-methods-use-this
async loadData(content: QnaPair): Promise<LoaderResult> {
const [question, answer] = content;
const contentText = `Q: ${question}\nA: ${answer}`;
const metaData = {
url: 'local',
};
return [
{
content: contentText,
metaData,
},
];
}
}
export { LocalQnaPairLoader };
@@ -0,0 +1,58 @@
import type { TextContent } from 'pdfjs-dist/types/src/display/api';
import type { LoaderResult, Metadata } from '../models';
import { cleanString } from '../utils';
import { BaseLoader } from './BaseLoader';
const pdfjsLib = require('pdfjs-dist');
interface Page {
page_content: string;
}
class PdfFileLoader extends BaseLoader {
static async getPagesFromPdf(url: string): Promise<Page[]> {
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
const { numPages } = pdf;
const promises = Array.from({ length: numPages }, async (_, i) => {
const page = await pdf.getPage(i + 1);
const pageText: TextContent = await page.getTextContent();
const pageContent: string = pageText.items
.map((item) => ('str' in item ? item.str : ''))
.join(' ');
return {
page_content: pageContent,
};
});
return Promise.all(promises);
}
// eslint-disable-next-line class-methods-use-this
async loadData(url: string): Promise<LoaderResult> {
const pages: Page[] = await PdfFileLoader.getPagesFromPdf(url);
const output: LoaderResult = [];
if (!pages.length) {
throw new Error('No data found');
}
pages.forEach((page) => {
let content: string = page.page_content;
content = cleanString(content);
const metaData: Metadata = {
url,
};
output.push({
content,
metaData,
});
});
return output;
}
}
export { PdfFileLoader };
@@ -0,0 +1,51 @@
import axios from 'axios';
import { JSDOM } from 'jsdom';
import { cleanString } from '../utils';
import { BaseLoader } from './BaseLoader';
class WebPageLoader extends BaseLoader {
// eslint-disable-next-line class-methods-use-this
async loadData(url: string) {
const response = await axios.get(url);
const html = response.data;
const dom = new JSDOM(html);
const { document } = dom.window;
const unwantedTags = [
'nav',
'aside',
'form',
'header',
'noscript',
'svg',
'canvas',
'footer',
'script',
'style',
];
unwantedTags.forEach((tagName) => {
const elements = document.getElementsByTagName(tagName);
Array.from(elements).forEach((element) => {
// eslint-disable-next-line no-param-reassign
(element as HTMLElement).textContent = ' ';
});
});
const output = [];
let content = document.body.textContent;
if (!content) {
throw new Error('Web page content is empty.');
}
content = cleanString(content);
const metaData = {
url,
};
output.push({
content,
metaData,
});
return output;
}
}
export { WebPageLoader };
@@ -0,0 +1,6 @@
import { BaseLoader } from './BaseLoader';
import { LocalQnaPairLoader } from './LocalQnaPair';
import { PdfFileLoader } from './PdfFile';
import { WebPageLoader } from './WebPage';
export { BaseLoader, LocalQnaPairLoader, PdfFileLoader, WebPageLoader };
@@ -0,0 +1,7 @@
import type { Metadata } from './Metadata';
export type ChunkResult = {
documents: string[];
ids: string[];
metadatas: Metadata[];
};
@@ -0,0 +1,10 @@
import type { ChunkResult } from './ChunkResult';
type Data = {
doc: ChunkResult['documents'][0];
meta: ChunkResult['metadatas'][0];
};
export type DataDict = {
[id: string]: Data;
};
@@ -0,0 +1 @@
export type DataType = 'pdf_file' | 'web_page' | 'qna_pair';
@@ -0,0 +1,3 @@
import type { Document } from 'langchain/document';
export type FormattedResult = [Document, number | null];
+7
View File
@@ -0,0 +1,7 @@
import type { QnaPair } from './QnAPair';
export type RemoteInput = string;
export type LocalInput = QnaPair;
export type Input = RemoteInput | LocalInput;
@@ -0,0 +1,3 @@
import type { Metadata } from './Metadata';
export type LoaderResult = { content: any; metaData: Metadata }[];
@@ -0,0 +1,3 @@
export type Metadata = {
url: string;
};
@@ -0,0 +1 @@
export type Method = 'init' | 'query' | 'add' | 'add_local';
@@ -0,0 +1,4 @@
type Question = string;
type Answer = string;
export type QnaPair = [Question, Answer];
+21
View File
@@ -0,0 +1,21 @@
import { DataDict } from './DataDict';
import { DataType } from './DataType';
import { FormattedResult } from './FormattedResult';
import { Input, LocalInput, RemoteInput } from './Input';
import { LoaderResult } from './LoaderResult';
import { Metadata } from './Metadata';
import { Method } from './Method';
import { QnaPair } from './QnAPair';
export {
DataDict,
DataType,
FormattedResult,
Input,
LoaderResult,
LocalInput,
Metadata,
Method,
QnaPair,
RemoteInput,
};
+26
View File
@@ -0,0 +1,26 @@
/**
* This function takes in a string and performs a series of text cleaning operations.
* @param {str} text: The text to be cleaned. This is expected to be a string.
* @returns {str}: The cleaned text after all the cleaning operations have been performed.
*/
export function cleanString(text: string): string {
// Replacement of newline characters:
let cleanedText = text.replace(/\n/g, ' ');
// Stripping and reducing multiple spaces to single:
cleanedText = cleanedText.trim().replace(/\s+/g, ' ');
// Removing backslashes:
cleanedText = cleanedText.replace(/\\/g, '');
// Replacing hash characters:
cleanedText = cleanedText.replace(/#/g, ' ');
// Eliminating consecutive non-alphanumeric characters:
// This regex identifies consecutive non-alphanumeric characters (i.e., not a word character [a-zA-Z0-9_] and not a whitespace) in the string
// and replaces each group of such characters with a single occurrence of that character.
// For example, "!!! hello !!!" would become "! hello !".
cleanedText = cleanedText.replace(/([^\w\s])\1*/g, '$1');
return cleanedText;
}
@@ -0,0 +1,14 @@
class BaseVectorDB {
initDb: Promise<void>;
constructor() {
this.initDb = this.getClientAndCollection();
}
// eslint-disable-next-line class-methods-use-this
protected async getClientAndCollection(): Promise<void> {
throw new Error('getClientAndCollection() method is not implemented');
}
}
export { BaseVectorDB };
@@ -0,0 +1,38 @@
import type { Collection } from 'chromadb';
import { ChromaClient, OpenAIEmbeddingFunction } from 'chromadb';
import { BaseVectorDB } from './BaseVectorDb';
const embedder = new OpenAIEmbeddingFunction({
openai_api_key: process.env.OPENAI_API_KEY ?? '',
});
class ChromaDB extends BaseVectorDB {
client: ChromaClient | undefined;
collection: Collection | null = null;
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor() {
super();
}
protected async getClientAndCollection(): Promise<void> {
this.client = new ChromaClient({ path: 'http://localhost:8000' });
try {
this.collection = await this.client.getCollection({
name: 'embedchain_store',
embeddingFunction: embedder,
});
} catch (err) {
if (!this.collection) {
this.collection = await this.client.createCollection({
name: 'embedchain_store',
embeddingFunction: embedder,
});
}
}
}
}
export { ChromaDB };
@@ -0,0 +1,3 @@
import { ChromaDB } from './ChromaDb';
export { ChromaDB };
+9
View File
@@ -0,0 +1,9 @@
const { EmbedChainApp } = require("./embedchain/embedchain");
async function App() {
const app = new EmbedChainApp();
await app.init_app;
return app;
}
module.exports = { App };
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['.d.ts'],
};
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
'*.{js,ts}': ['eslint --fix', 'eslint'],
'**/*.ts?(x)': () => 'npm run check-types',
'*.json': ['prettier --write'],
};
+18457
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
{
"name": "embedchain",
"version": "0.0.8",
"description": "embedchain is a framework to easily create LLM powered bots over any dataset",
"main": "dist/index.js",
"types": "types/index.d.ts",
"files": [
"dist",
"types"
],
"scripts": {
"build": "tsc -p tsconfig.build.json --listFiles",
"prepare": "husky install",
"test": "jest",
"check-types": "tsc --noEmit --pretty"
},
"author": "Taranjeet Singh",
"license": "Apache-2.0",
"dependencies": {
"axios": "^1.4.0",
"chromadb": "^1.5.6",
"jsdom": "^22.1.0",
"langchain": "^0.0.136",
"openai": "^4.3.1",
"pdfjs-dist": "^3.8.162",
"uuid": "^9.0.0"
},
"devDependencies": {
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@commitlint/cz-commitlint": "^17.1.2",
"@types/jest": "^29.5.1",
"@types/jsdom": "^21.1.1",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"eslint": "^8.34.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-testing-library": "^5.9.1",
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^8.0.1",
"jest": "^29.5.0",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.2",
"typescript": "^5.2.2"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["embedchain/__tests__"]
}
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es6",
"module": "CommonJS",
"strict": true,
"outDir": "dist",
"rootDir": "embedchain",
"sourceMap": true,
"declaration": true,
"declarationDir": "types",
"esModuleInterop": true
},
"include": ["embedchain/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
+4 -3
View File
@@ -2,9 +2,10 @@ import importlib.metadata
__version__ = importlib.metadata.version(__package__ or __name__)
from embedchain.apps.App import App # noqa: F401
from embedchain.apps.CustomApp import CustomApp # noqa: F401
from embedchain.apps.app import App # noqa: F401
from embedchain.apps.custom_app import CustomApp # noqa: F401
from embedchain.apps.Llama2App import Llama2App # noqa: F401
from embedchain.apps.OpenSourceApp import OpenSourceApp # noqa: F401
from embedchain.apps.open_source_app import OpenSourceApp # noqa: F401
from embedchain.apps.PersonApp import (PersonApp, # noqa: F401
PersonOpenSourceApp)
from embedchain.vectordb.chroma import ChromaDB # noqa: F401
-51
View File
@@ -1,51 +0,0 @@
import openai
from embedchain.config import AppConfig, ChatConfig
from embedchain.embedchain import EmbedChain
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: AppConfig = None):
"""
:param config: AppConfig instance to load as configuration. Optional.
"""
if config is None:
config = AppConfig()
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig):
messages = []
if config.system_prompt:
messages.append({"role": "system", "content": config.system_prompt})
messages.append({"role": "user", "content": prompt})
response = openai.ChatCompletion.create(
model=config.model or "gpt-3.5-turbo-0613",
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
-156
View File
@@ -1,156 +0,0 @@
import logging
from typing import List, Optional
from langchain.schema import BaseMessage
from embedchain.config import ChatConfig, CustomAppConfig
from embedchain.embedchain import EmbedChain
from embedchain.models import Providers
class CustomApp(EmbedChain):
"""
The custom 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: CustomAppConfig = None):
"""
:param config: Optional. `CustomAppConfig` instance to load as configuration.
:raises ValueError: Config must be provided for custom app
"""
if config is None:
raise ValueError("Config must be provided for custom app")
self.provider = config.provider
if config.provider == Providers.GPT4ALL:
from embedchain import OpenSourceApp
# Because these models run locally, they should have an instance running when the custom app is created
self.open_source_app = OpenSourceApp(config=config.open_source_app_config)
super().__init__(config)
def set_llm_model(self, provider: Providers):
self.provider = provider
if provider == Providers.GPT4ALL:
raise ValueError(
"GPT4ALL needs to be instantiated with the model known, please create a new app instance instead"
)
def get_llm_model_answer(self, prompt, config: ChatConfig):
# TODO: Quitting the streaming response here for now.
# Idea: https://gist.github.com/jvelezmagic/03ddf4c452d011aae36b2a0f73d72f68
if config.stream:
raise NotImplementedError(
"Streaming responses have not been implemented for this model yet. Please disable."
)
try:
if self.provider == Providers.OPENAI:
return CustomApp._get_openai_answer(prompt, config)
if self.provider == Providers.ANTHROPHIC:
return CustomApp._get_athrophic_answer(prompt, config)
if self.provider == Providers.VERTEX_AI:
return CustomApp._get_vertex_answer(prompt, config)
if self.provider == Providers.GPT4ALL:
return self.open_source_app._get_gpt4all_answer(prompt, config)
if self.provider == Providers.AZURE_OPENAI:
return CustomApp._get_azure_openai_answer(prompt, config)
except ImportError as e:
raise ModuleNotFoundError(e.msg) from None
@staticmethod
def _get_openai_answer(prompt: str, config: ChatConfig) -> str:
from langchain.chat_models import ChatOpenAI
chat = ChatOpenAI(
temperature=config.temperature,
model=config.model or "gpt-3.5-turbo",
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 = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
@staticmethod
def _get_athrophic_answer(prompt: str, config: ChatConfig) -> 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 = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
@staticmethod
def _get_vertex_answer(prompt: str, config: ChatConfig) -> str:
from langchain.chat_models import ChatVertexAI
chat = ChatVertexAI(temperature=config.temperature, model=config.model, max_output_tokens=config.max_tokens)
if config.top_p and config.top_p != 1:
logging.warning("Config option `top_p` is not supported by this model.")
messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
@staticmethod
def _get_azure_openai_answer(prompt: str, config: ChatConfig) -> 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 = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
@staticmethod
def _get_messages(prompt: str, system_prompt: Optional[str] = None) -> 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
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
+26 -26
View File
@@ -1,38 +1,38 @@
import os
import logging
from typing import Optional
from langchain.llms import Replicate
from embedchain.config import AppConfig, ChatConfig
from embedchain.embedchain import EmbedChain
from embedchain.apps.app import App
from embedchain.config import CustomAppConfig
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.llama2 import Llama2Llm
class Llama2App(EmbedChain):
@register_deserializable
class Llama2App(App):
"""
The EmbedChain Llama2App class.
Has two functions: add and query.
adds(data_type, url): adds the data from the given URL to the vector db.
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.
.. deprecated:: 0.0.64
Use `App` instead.
"""
def __init__(self, config: AppConfig = None):
def __init__(self, config: CustomAppConfig = None, system_prompt: Optional[str] = None):
"""
:param config: AppConfig instance to load as configuration. Optional.
.. deprecated:: 0.0.64
Use `App` instead.
:param config: CustomAppConfig instance to load as configuration. Optional.
:param system_prompt: System prompt string. Optional.
"""
if "REPLICATE_API_TOKEN" not in os.environ:
raise ValueError("Please set the REPLICATE_API_TOKEN environment variable.")
if config is None:
config = AppConfig()
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig = None):
# TODO: Move the model and other inputs into config
if config.system_prompt:
raise ValueError("Llama2App does not support `system_prompt`")
llm = Replicate(
model="a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
input={"temperature": 0.75, "max_length": 500, "top_p": 1},
logging.warning(
"DEPRECATION WARNING: Please use `App` instead of `Llama2App`. "
"`Llama2App` will be removed in a future release. "
"Please refer to https://docs.embedchain.ai/advanced/app_types#llama2app for instructions."
)
return llm(prompt)
super().__init__(config=config, llm=Llama2Llm(), system_prompt=system_prompt)
-68
View File
@@ -1,68 +0,0 @@
import logging
from typing import Iterable, Union
from embedchain.config import ChatConfig, OpenSourceAppConfig
from embedchain.embedchain import EmbedChain
gpt4all_model = None
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: OpenSourceAppConfig = None):
"""
:param config: OpenSourceAppConfig instance to load as configuration. Optional.
`ef` defaults to open source.
"""
logging.info("Loading open source embedding model. This may take some time...") # noqa:E501
if not config:
config = OpenSourceAppConfig()
if not config.model:
raise ValueError("OpenSourceApp needs a model to be instantiated. Maybe you passed the wrong config type?")
self.instance = OpenSourceApp._get_instance(config.model)
logging.info("Successfully loaded open source embedding model.")
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig):
return self._get_gpt4all_answer(prompt=prompt, config=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)
def _get_gpt4all_answer(self, prompt: str, config: ChatConfig) -> 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
+33 -19
View File
@@ -1,13 +1,15 @@
from string import Template
from embedchain.apps.App import App
from embedchain.apps.OpenSourceApp import OpenSourceApp
from embedchain.config import ChatConfig, QueryConfig
from embedchain.config.apps.BaseAppConfig import BaseAppConfig
from embedchain.config.QueryConfig import (DEFAULT_PROMPT,
DEFAULT_PROMPT_WITH_HISTORY)
from embedchain.apps.app import App
from embedchain.apps.open_source_app import OpenSourceApp
from embedchain.config import BaseLlmConfig
from embedchain.config.apps.base_app_config import BaseAppConfig
from embedchain.config.llm.base_llm_config import (DEFAULT_PROMPT,
DEFAULT_PROMPT_WITH_HISTORY)
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class EmbedChainPersonApp:
"""
Base class to create a person bot.
@@ -17,20 +19,30 @@ class EmbedChainPersonApp:
:param config: BaseAppConfig instance to load as configuration.
"""
def __init__(self, person, config: BaseAppConfig = None):
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: ChatConfig = None):
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
:param config: Optional. The `ChatConfig` instance to use as
configuration options.
: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)
@@ -43,38 +55,40 @@ class EmbedChainPersonApp:
config.template = template
else:
# if no config is present at all, initialize the config with person prompt and default template
config = QueryConfig(
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: QueryConfig = None, dry_run=False):
config = self.add_person_template_to_config(DEFAULT_PROMPT, config)
return super().query(input_query, config, dry_run)
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: ChatConfig = None, dry_run=False):
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)
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: QueryConfig = None, dry_run=False):
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: ChatConfig = None, dry_run=False):
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)
+125
View File
@@ -0,0 +1,125 @@
import logging
from typing import Optional
from embedchain.config import (AppConfig, BaseEmbedderConfig, BaseLlmConfig,
ChromaDbConfig)
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.embedchain import EmbedChain
from embedchain.embedder.base import BaseEmbedder
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.base import BaseVectorDB
from embedchain.vectordb.chroma 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: Optional[AppConfig] = None,
llm: BaseLlm = None,
llm_config: Optional[BaseLlmConfig] = None,
db: BaseVectorDB = None,
db_config: Optional[BaseVectorDbConfig] = None,
embedder: BaseEmbedder = None,
embedder_config: Optional[BaseEmbedderConfig] = None,
chromadb_config: Optional[ChromaDbConfig] = None,
system_prompt: Optional[str] = None,
):
"""
Initialize a new `App` instance.
:param config: Config for the app instance., defaults to None
:type config: Optional[AppConfig], optional
:param llm: LLM Class instance. example: `from embedchain.llm.openai import OpenAILlm`, defaults to OpenAiLlm
:type llm: BaseLlm, 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: Optional[BaseLlmConfig], optional
:param db: The database to use for storing and retrieving embeddings,
example: `from embedchain.vectordb.chroma_db import ChromaDb`, defaults to ChromaDb
:type db: BaseVectorDB, optional
:param db_config: Allows you to configure the vector database,
example: `from embedchain.config import ChromaDbConfig`, defaults to None
:type db_config: Optional[BaseVectorDbConfig], optional
:param embedder: The embedder (embedding model and function) use to calculate embeddings.
example: `from embedchain.embedder.gpt4all_embedder import GPT4AllEmbedder`, defaults to OpenAIEmbedder
:type embedder: BaseEmbedder, optional
:param embedder_config: Allows you to configure the Embedder.
example: `from embedchain.config import BaseEmbedderConfig`, defaults to None
:type embedder_config: Optional[BaseEmbedderConfig], optional
:param chromadb_config: Deprecated alias of `db_config`, 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
:raises TypeError: LLM, database or embedder or their config is not a valid class instance.
"""
# Overwrite deprecated arguments
if chromadb_config:
logging.warning(
"DEPRECATION WARNING: Please use `db_config` argument instead of `chromadb_config`."
"`chromadb_config` will be removed in a future release."
)
db_config = chromadb_config
# Type check configs
if config and not isinstance(config, AppConfig):
raise TypeError(
"Config is not a `AppConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if llm_config and not isinstance(llm_config, BaseLlmConfig):
raise TypeError(
"`llm_config` is not a `BaseLlmConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if db_config and not isinstance(db_config, BaseVectorDbConfig):
raise TypeError(
"`db_config` is not a `BaseVectorDbConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if embedder_config and not isinstance(embedder_config, BaseEmbedderConfig):
raise TypeError(
"`embedder_config` is not a `BaseEmbedderConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
# Assign defaults
if config is None:
config = AppConfig()
if llm is None:
llm = OpenAILlm(config=llm_config)
if db is None:
db = ChromaDB(config=db_config)
if embedder is None:
embedder = OpenAIEmbedder(config=embedder_config)
# Type check assignments
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, llm=llm, db=db, embedder=embedder, system_prompt=system_prompt)
+63
View File
@@ -0,0 +1,63 @@
import logging
from typing import Optional
from embedchain.apps.app import App
from embedchain.config import CustomAppConfig
from embedchain.embedder.base import BaseEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
from embedchain.vectordb.base import BaseVectorDB
@register_deserializable
class CustomApp(App):
"""
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.
.. deprecated:: 0.0.64
Use `App` instead.
"""
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.
.. deprecated:: 0.0.64
Use `App` instead.
: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 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.
"""
logging.warning(
"DEPRECATION WARNING: Please use `App` instead of `CustomApp`. "
"`CustomApp` will be removed in a future release. "
"Please refer to https://docs.embedchain.ai/advanced/app_types#opensourceapp for instructions."
)
super().__init__(config=config, llm=llm, db=db, embedder=embedder, system_prompt=system_prompt)
+71
View File
@@ -0,0 +1,71 @@
import logging
from typing import Optional
from embedchain.apps.app import App
from embedchain.config import (BaseLlmConfig, ChromaDbConfig,
OpenSourceAppConfig)
from embedchain.embedder.gpt4all import GPT4AllEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.gpt4all import GPT4ALLLlm
from embedchain.vectordb.chroma import ChromaDB
gpt4all_model = None
@register_deserializable
class OpenSourceApp(App):
"""
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.
.. deprecated:: 0.0.64
Use `App` instead.
"""
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.
.. deprecated:: 0.0.64
Use `App` instead.
: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.warning(
"DEPRECATION WARNING: Please use `App` instead of `OpenSourceApp`."
"`OpenSourceApp` will be removed in a future release."
"Please refer to https://docs.embedchain.ai/advanced/app_types#customapp for instructions."
)
super().__init__(
config=config,
llm=GPT4ALLLlm(config=llm_config),
db=ChromaDB(config=chromadb_config),
embedder=GPT4AllEmbedder(),
system_prompt=system_prompt,
)
+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
+34 -13
View File
@@ -1,23 +1,44 @@
from typing import Any
from embedchain import CustomApp
from embedchain.config import AddConfig, CustomAppConfig, QueryConfig
from embedchain.models import EmbeddingFunctions, Providers
from embedchain.config import AddConfig, CustomAppConfig, LlmConfig
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.helper.json_serializable import (JSONSerializable,
register_deserializable)
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.chroma import ChromaDB
class BaseBot:
def __init__(self, app_config=None):
if app_config is None:
app_config = CustomAppConfig(embedding_fn=EmbeddingFunctions.OPENAI, provider=Providers.OPENAI)
self.app_config = app_config
self.app = CustomApp(config=self.app_config)
@register_deserializable
class BaseBot(JSONSerializable):
def __init__(self):
self.app = CustomApp(config=CustomAppConfig(), llm=OpenAILlm(), db=ChromaDB(), embedder=OpenAIEmbedder())
def add(self, data, config: AddConfig = None):
"""Add data to the bot"""
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, config: QueryConfig = None):
"""Query bot"""
config = config if config else QueryConfig()
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):
+127
View File
@@ -0,0 +1,127 @@
import argparse
import logging
import os
from embedchain.helper.json_serializable import register_deserializable
from .base import BaseBot
try:
import discord
from discord import app_commands
from discord.ext import commands
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for Discord are not installed."
'Please install with `pip install "embedchain[discord]"`'
) from None
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()
+87
View File
@@ -0,0 +1,87 @@
import argparse
import logging
import os
from typing import List, Optional
from embedchain.helper.json_serializable import register_deserializable
from .base import BaseBot
try:
from fastapi_poe import PoeBot, run
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for Poe are not installed." 'Please install with `pip install "embedchain[poe]"`'
) from None
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()
+99
View File
@@ -0,0 +1,99 @@
import argparse
import logging
import os
import signal
import sys
from embedchain import App
from embedchain.helper.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 --upgrade "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()
+14 -5
View File
@@ -1,16 +1,25 @@
import argparse
import importlib
import logging
import signal
import sys
from flask import Flask, request
from twilio.twiml.messaging_response import MessagingResponse
from embedchain.helper.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):
@@ -39,7 +48,7 @@ class WhatsAppBot(BaseBot):
return response
def start(self, host="0.0.0.0", port=5000, debug=True):
app = Flask(__name__)
app = self.flask.Flask(__name__)
def signal_handler(sig, frame):
logging.info("\nGracefully shutting down the WhatsAppBot...")
@@ -49,9 +58,9 @@ class WhatsAppBot(BaseBot):
@app.route("/chat", methods=["POST"])
def chat():
incoming_message = request.values.get("Body", "").lower()
incoming_message = self.flask.request.values.get("Body", "").lower()
response = self.handle_message(incoming_message)
twilio_response = MessagingResponse()
twilio_response = self.twilio.twiml.messaging_response.MessagingResponse()
twilio_response.message(response)
return str(twilio_response)
+8 -3
View File
@@ -1,9 +1,10 @@
import hashlib
from embedchain.helper.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
@@ -21,14 +22,17 @@ class BaseChunker:
documents = []
ids = []
idMap = {}
datas = loader.load_data(src)
data_result = loader.load_data(src)
data_records = data_result["data"]
doc_id = data_result["doc_id"]
metadatas = []
for data in datas:
for data in data_records:
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
meta_data["doc_id"] = doc_id
url = meta_data["url"]
chunks = self.get_chunks(content)
@@ -44,6 +48,7 @@ class BaseChunker:
"documents": documents,
"ids": ids,
"metadatas": metadatas,
"doc_id": doc_id,
}
def get_chunks(self, content):
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class DocsSiteChunker(BaseChunker):
"""Chunker for code docs site."""
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.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.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class MdxChunker(BaseChunker):
"""Chunker for mdx files."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=1000, 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)
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class NotionChunker(BaseChunker):
"""Chunker for notion."""
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class PdfFileChunker(BaseChunker):
"""Chunker for PDF file."""
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.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.add_config 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)
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class TextChunker(BaseChunker):
"""Chunker for text."""
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class WebPageChunker(BaseChunker):
"""Chunker for web page."""
+3 -1
View File
@@ -3,9 +3,11 @@ 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.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class YoutubeVideoChunker(BaseChunker):
"""Chunker for Youtube video."""
-10
View File
@@ -1,10 +0,0 @@
class BaseConfig:
"""
Base config.
"""
def __init__(self):
pass
def as_dict(self):
return vars(self)
-87
View File
@@ -1,87 +0,0 @@
from string import Template
from typing import Optional
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,
deployment_name=None,
system_prompt: Optional[str] = None,
):
"""
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
:param deployment_name: t.b.a.
:param system_prompt: Optional. System prompt string.
: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,
deployment_name=deployment_name,
system_prompt=system_prompt,
)
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
-143
View File
@@ -1,143 +0,0 @@
import re
from string import Template
from typing import Optional
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,
deployment_name=None,
system_prompt: Optional[str] = None,
):
"""
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
:param deployment_name: t.b.a.
:param system_prompt: Optional. System prompt string.
: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
self.top_p = top_p if top_p else 1
self.deployment_name = deployment_name
self.system_prompt = system_prompt
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)
)
+14 -9
View File
@@ -1,9 +1,14 @@
from .AddConfig import AddConfig, ChunkerConfig # noqa: F401
from .apps.AppConfig import AppConfig # noqa: F401
from .apps.CustomAppConfig import CustomAppConfig # noqa: F401
from .apps.OpenSourceAppConfig import OpenSourceAppConfig # noqa: F401
from .BaseConfig import BaseConfig # noqa: F401
from .ChatConfig import ChatConfig # noqa: F401
from .QueryConfig import QueryConfig # noqa: F401
from .vectordbs.ElasticsearchDBConfig import \
ElasticsearchDBConfig # noqa: F401
# flake8: noqa: F401
from .add_config import AddConfig, ChunkerConfig
from .apps.app_config import AppConfig
from .apps.custom_app_config import CustomAppConfig
from .apps.open_source_app_config import OpenSourceAppConfig
from .base_config import BaseConfig
from .embedder.base import BaseEmbedderConfig
from .embedder.base import BaseEmbedderConfig as EmbedderConfig
from .llm.base_llm_config import BaseLlmConfig
from .llm.base_llm_config import BaseLlmConfig as LlmConfig
from .vectordb.chroma import ChromaDbConfig
from .vectordb.elasticsearch import ElasticsearchDBConfig
from .vectordb.opensearch import OpenSearchDBConfig
@@ -1,8 +1,10 @@
from typing import Callable, Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.config.base_config import BaseConfig
from embedchain.helper.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
-63
View File
@@ -1,63 +0,0 @@
import os
from typing import Optional
try:
from chromadb.utils import embedding_functions
except RuntimeError:
from embedchain.utils import use_pysqlite3
use_pysqlite3()
from chromadb.utils import embedding_functions
from .BaseAppConfig import BaseAppConfig
class AppConfig(BaseAppConfig):
"""
Config to initialize an embedchain custom `App` instance, with extra config options.
"""
def __init__(
self,
log_level=None,
host=None,
port=None,
id=None,
collection_name=None,
collect_metrics: Optional[bool] = None,
):
"""
:param log_level: Optional. (String) Debug level
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'].
:param host: Optional. Hostname for the database server.
:param port: Optional. Port for the database server.
:param id: Optional. ID of the app. Document metadata will have this id.
:param collection_name: Optional. Collection name for the database.
:param collect_metrics: Defaults to True. Send anonymous telemetry to improve embedchain.
"""
super().__init__(
log_level=log_level,
embedding_fn=AppConfig.default_embedding_function(),
host=host,
port=port,
id=id,
collection_name=collection_name,
collect_metrics=collect_metrics,
)
@staticmethod
def default_embedding_function():
"""
Sets embedding function to default (`text-embedding-ada-002`).
:raises ValueError: If the template is not valid as template should contain
$context and $query
:returns: The default embedding function for the app class.
"""
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
return embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name="text-embedding-ada-002",
)
-98
View File
@@ -1,98 +0,0 @@
import logging
from embedchain.config.BaseConfig import BaseConfig
from embedchain.config.vectordbs import ElasticsearchDBConfig
from embedchain.models import VectorDatabases, VectorDimensions
class BaseAppConfig(BaseConfig):
"""
Parent config to initialize an instance of `App`, `OpenSourceApp` or `CustomApp`.
"""
def __init__(
self,
log_level=None,
embedding_fn=None,
db=None,
host=None,
port=None,
id=None,
collection_name=None,
collect_metrics: bool = True,
db_type: VectorDatabases = None,
vector_dim: VectorDimensions = None,
es_config: ElasticsearchDBConfig = None,
):
"""
:param log_level: Optional. (String) Debug level
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'].
:param embedding_fn: Embedding function to use.
:param db: Optional. (Vector) database instance to use for embeddings.
:param host: Optional. Hostname for the database server.
:param port: Optional. Port for the database server.
:param id: Optional. ID of the app. Document metadata will have this id.
:param collection_name: Optional. Collection name for the database.
:param collect_metrics: Defaults to True. Send anonymous telemetry to improve embedchain.
:param db_type: Optional. type of Vector database to use
:param vector_dim: Vector dimension generated by embedding fn
:param es_config: Optional. elasticsearch database config to be used for connection
"""
self._setup_logging(log_level)
self.collection_name = collection_name if collection_name else "embedchain_store"
self.db = BaseAppConfig.get_db(
db=db,
embedding_fn=embedding_fn,
host=host,
port=port,
db_type=db_type,
vector_dim=vector_dim,
collection_name=self.collection_name,
es_config=es_config,
)
self.id = id
self.collect_metrics = True if (collect_metrics is True or collect_metrics is None) else False
return
@staticmethod
def get_db(db, embedding_fn, host, port, db_type, vector_dim, collection_name, es_config):
"""
Get db based on db_type, db with default database (`ChromaDb`)
:param Optional. (Vector) database to use for embeddings.
:param embedding_fn: Embedding function to use in database.
:param host: Optional. Hostname for the database server.
:param port: Optional. Port for the database server.
:param db_type: Optional. db type to use. Supported values (`es`, `chroma`)
:param vector_dim: Vector dimension generated by embedding fn
:param collection_name: Optional. Collection name for the database.
:param es_config: Optional. elasticsearch database config to be used for connection
:raises ValueError: BaseAppConfig knows no default embedding function.
:returns: database instance
"""
if db:
return db
if embedding_fn is None:
raise ValueError("ChromaDb cannot be instantiated without an embedding function")
if db_type == VectorDatabases.ELASTICSEARCH:
from embedchain.vectordb.elasticsearch_db import ElasticsearchDB
return ElasticsearchDB(
embedding_fn=embedding_fn, vector_dim=vector_dim, collection_name=collection_name, es_config=es_config
)
from embedchain.vectordb.chroma_db import ChromaDB
return ChromaDB(embedding_fn=embedding_fn, host=host, port=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

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