Compare commits

..

86 Commits

Author SHA1 Message Date
Taranjeet Singh e56f91a239 Bump version to 0.0.35 (#424) 2023-08-11 09:29:31 +05:30
Prashant Chaudhary 0179141b2e feat: add support for Elastcisearch as vector data source (#402) 2023-08-11 09:23:56 +05:30
cachho f0abfea55d chore: linting (#414) 2023-08-11 01:53:42 +05:30
Taranjeet Singh 77e223be52 Bump version to 0.0.34 (#420) 2023-08-10 04:48:34 +05:30
Taranjeet Singh c96df72cd0 Fix: lazy load Notion loader (#419) 2023-08-10 04:44:02 +05:30
cachho ce6eb39009 feat: notion loader (#405) 2023-08-09 13:15:22 +05:30
Jonas eeac84e2d9 feat: collection name everywhere (#310)
Co-authored-by: cachho <admin@ch-webdev.com>
2023-08-09 13:08:35 +05:30
Taranjeet Singh 1ee1e671d1 Bump version to 0.0.33 (#411) 2023-08-09 12:59:45 +05:30
cachho 2ef7c0b736 fix: escape pysqlite swapping (#410) 2023-08-09 12:54:41 +05:30
aryankhanna475 f2b563e42a Additions to the community showcase (#401)
Co-authored-by: Sahil Kumar Yadav <sahilyadav902@gmail.com>
2023-08-09 12:39:36 +05:30
Taranjeet Singh 7a718643a3 bump version to 0.0.32 (#409) 2023-08-09 12:28:35 +05:30
Taranjeet Singh 1f0f0c93b7 fix: Pass deployment name as param for azure api (#406) 2023-08-09 12:25:26 +05:30
Taranjeet Singh 030e3521a9 Bump version to 0.0.31 (#408) 2023-08-09 12:17:57 +05:30
cachho fdf5d1928d test: added chunker unit tests (#325) 2023-08-09 09:12:30 +05:30
cachho 65011a67d4 fix: is readable - zero division error (#383) 2023-08-09 09:06:26 +05:30
Sahil Kumar Yadav ec09a8a6fc example: embedchain playground (#384) 2023-08-08 08:43:05 -07:00
cachho 5e94980aaa fix: no logging in pysqlite replacement (#378) 2023-07-27 07:00:01 -07:00
aryankhanna475 8b619756b6 update: docs showcase (#377) 2023-07-27 06:58:22 -07:00
cachho 35b43edb20 fix: typo in readme example (#373) 2023-07-27 06:56:48 -07:00
cachho 02cbde2fc1 docs: fix argument out of order (#374) 2023-07-27 06:56:18 -07:00
cachho a868fce036 fix: remove debug logging (#379) 2023-07-27 06:55:00 -07:00
cachho 079e35b205 fix: chroma pysqlite version (#350) 2023-07-27 13:12:27 +05:30
Deshraj Yadav 8c91b75b98 [Feature]: Add support for azure openai model (#372) 2023-07-27 13:03:32 +05:30
cachho 55bfd7cafe refactor: loader chunker typing (#324) 2023-07-26 23:14:57 +05:30
cachho a8552686b4 docs: add back query config (#365) 2023-07-26 23:13:56 +05:30
Alessandro Panzieri 12a2f78dcb add "d" on "Embedchain" in citation section title (#369) 2023-07-26 22:57:01 +05:30
aaishikdutta cce6d5ddab fix: Personapp not working with config (#368) 2023-07-26 22:04:11 +05:30
cachho 088346c4fc docs: fix template (#364) 2023-07-25 00:46:50 -07:00
aaishikdutta 7fa7b9e199 Update discord badge in readme.md (#361) 2023-07-24 09:19:54 -07:00
aaishikdutta cac15c147f added fix for documentation (#362) 2023-07-24 01:18:37 -07:00
aaishikdutta c54dd1e7bb Fixed test case for chroma db (#358) 2023-07-22 16:17:01 -07:00
aaishikdutta c9c56a4b26 fixed dry_run not working in PersonApp (#357) 2023-07-21 22:59:20 -07:00
Taranjeet Singh acbdb800d3 Bump version to 0.0.30 (#355) 2023-07-22 07:44:38 +05:30
Taranjeet Singh 49711c92b7 bug: fix online bug in chat endpoint (#354) 2023-07-22 07:42:17 +05:30
aaishikdutta c4797eb121 fix: fix PersonOpenSourceApp query error (#353) 2023-07-22 00:14:07 +05:30
Taranjeet Singh bea4e9e6d1 Bump version to 0.0.29 (#349) 2023-07-21 13:10:21 +05:30
Taranjeet Singh 9687ec2c1a fix: init metadata as empty (#348) 2023-07-21 13:08:46 +05:30
Taranjeet Singh 09da4a3002 Fix package version in setup.py (#345) 2023-07-21 08:21:30 +05:30
Taranjeet Singh 93004306f2 Bump version to 0.0.28 (#344) 2023-07-21 08:16:40 +05:30
juaneloDev aed894246f fix: add metadata in add and add_local (#343) 2023-07-21 08:10:01 +05:30
Taranjeet Singh fa63a16591 bug: Chroma needs diff client as per settings (#342) 2023-07-21 06:47:01 +05:30
Taranjeet Singh 40643663cb Bump chroma version to 0.4 in setup.py (#341) 2023-07-21 03:28:24 +05:30
Candido Sales Gomes d590e4423b update: chroma v0.4.0 (#330) 2023-07-21 02:47:53 +05:30
Taranjeet Singh cdbf75e0ec Bump version to 0.0.27 (#337) 2023-07-21 00:06:54 +05:30
Taranjeet Singh 8216e05784 Bump version to 0.0.26 (#333) 2023-07-20 12:43:44 +05:30
Deshraj Yadav 2d4c51aa16 Fix error message on missing replicate api token (#332) 2023-07-20 12:42:27 +05:30
Deshraj Yadav cc43846d42 [feat]: add support for llama2 model (#331) 2023-07-20 12:31:37 +05:30
Sahil Kumar Yadav 3bdec3b71a fix: ValueError: ChromaDb cannot be instantiated without an embedding function (#312) 2023-07-20 12:22:41 +05:30
cachho a22a435690 docs: explain AddConfig (#327) 2023-07-20 12:03:38 +05:30
cachho a681d47bce fix: docs_site use chunker config implementation (#326) 2023-07-20 11:59:59 +05:30
aaishikdutta 4bb06147c1 [BREAKING CHANGE] moved dry run into query and chat (#329)
Co-authored-by: Aaishik Dutta <aaishikdutta@Aaishiks-MacBook-Pro.local>
2023-07-20 11:55:41 +05:30
cachho 6b61b7e9c1 perf: don't instantiate every class in dict (#328) 2023-07-20 11:52:44 +05:30
cachho 91033c7221 chore: update pr template (#323) 2023-07-19 01:25:17 -07:00
Taranjeet Singh ab9f005885 docs: add provider, embedding function (#318) 2023-07-19 05:53:28 +05:30
cachho 3da5724853 feat: filter sitemap (#304) 2023-07-19 05:36:39 +05:30
aaishikdutta c12362486f feat: added data format to metadata internally (#314) 2023-07-19 05:35:43 +05:30
cachho d16eafae05 fix: format lint (#316) 2023-07-19 02:22:09 +05:30
cachho bb1fbba161 fix: add isort dev dependency (#315) 2023-07-19 02:17:54 +05:30
cachho adb7206639 feat: add new custom app (#313) 2023-07-19 00:54:23 +05:30
cachho 96143ac496 docs: app config instead of init config (#308) 2023-07-18 12:46:06 +05:30
cachho 1df804e7df fix: delete Apps (#307) 2023-07-18 12:40:37 +05:30
cachho 0ea278f633 refactor: app design concept (#305) 2023-07-17 16:20:26 -07:00
Taranjeet Singh 7ed46260b3 Bump version to 0.0.24 (#302) 2023-07-17 23:46:24 +05:30
cachho 9c58627372 chore: load chunker from config (#270) 2023-07-17 21:24:35 +05:30
cachho 07ba65d88d chore: documentation handling (#296) 2023-07-17 07:29:14 -07:00
Deshraj Yadav cf9638e7b2 example: fix notebook for docs site loader (#294) 2023-07-16 22:29:17 -07:00
Deshraj Yadav a548863a09 Feature: Add support for loading docs website (#293) 2023-07-16 22:22:52 -07:00
Sahil Kumar Yadav d5e40e1853 fix: template for personapp (#282) 2023-07-17 07:40:36 +05:30
Taranjeet Singh a4708b3b86 fix: Rename app usage docs (#292) 2023-07-17 07:34:29 +05:30
Taranjeet Singh 81c8cc62a2 feat: Add browse the internet or online functionality. (#291) 2023-07-17 07:29:58 +05:30
Taranjeet Singh e8b3d53faf fix: Handle divide by zero error when original size is 0 (#290) 2023-07-17 07:19:47 +05:30
Taranjeet Singh 2889799f10 fix: Dont initialize chroma and embedding function in init config. (#289) 2023-07-17 07:13:52 +05:30
Deshraj Yadav e24063caff docs: fix typos in readme (#288) 2023-07-16 16:36:58 -07:00
Deshraj Yadav c595003481 docs: setup docs for embedchain (#287) 2023-07-16 16:33:30 -07:00
Deshraj Yadav 05a4eef6ae chores: run lint and format (#284) 2023-07-15 21:34:06 -07:00
ma-raza ac68986404 Add project tools and contributing guidelines (#281) 2023-07-15 21:08:05 -07:00
cachho 3f71050c47 tests: added tests (#250) 2023-07-15 17:28:51 -07:00
cachho d12aeec1ff chore: remove duplicate dev requirements (#268) 2023-07-16 01:01:24 +05:30
cachho addf1c0666 feat: exclude by class, id in web_page data type and add logging (#273) 2023-07-16 00:51:25 +05:30
Shashank Srivastava d4b8542207 feat: Adding app id in metadata while reading and writing to vector db (#189) 2023-07-16 00:48:04 +05:30
Deshraj Yadav fd97fb268a feat: Update line length to 120 chars (#278) 2023-07-15 19:41:55 +05:30
Rayhan Patel 4f722621fd Updated README.md file for metadata (#279) 2023-07-15 00:02:52 -07:00
Taranjeet Singh 8ad6a5b5e7 Bump version to 0.0.23 (#277) 2023-07-15 09:15:18 +05:30
Taranjeet Singh 2d6e860175 bug: Fix import issue in setup (#275) 2023-07-15 09:11:40 +05:30
Taranjeet Singh 86e4146126 feat: Add new data type: code_docs_loader (#274) 2023-07-15 09:02:11 +05:30
cachho cd0c7bc971 fix: escape bs4 parsing error (#271) 2023-07-15 08:50:11 +05:30
158 changed files with 17046 additions and 1064 deletions
+1
View File
@@ -0,0 +1 @@
OPENAI_API_KEY=
+41
View File
@@ -0,0 +1,41 @@
name: 🐛 Bug Report
description: Create a report to help us reproduce and fix the bug
body:
- type: markdown
attributes:
value: >
#### Before submitting a bug, please make sure the issue hasn't been already addressed by searching through [the existing and past issues](https://github.com/gventuri/pandas-ai/issues?q=is%3Aissue+sort%3Acreated-desc+).
- type: textarea
attributes:
label: 🐛 Describe the bug
description: |
Please provide a clear and concise description of what the bug is.
If relevant, add a minimal example so that we can reproduce the error by running the code. It is very important for the snippet to be as succinct (minimal) as possible, so please take time to trim down any irrelevant code to help us debug efficiently. We are going to copy-paste your code and we expect to get the same result as you did: avoid any external data, and include the relevant imports, etc. For example:
```python
# All necessary imports at the beginning
import embedchain as ec
# Your code goes here
```
Please also paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full** traceback of the exception. It may be relevant to wrap error messages in ```` ```triple quotes blocks``` ````.
placeholder: |
A clear and concise description of what the bug is.
```python
Sample code to reproduce the problem
```
```
The error message you got, with the full traceback.
````
validations:
required: true
- type: markdown
attributes:
value: >
Thanks for contributing 🎉!
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: true
@@ -0,0 +1,32 @@
name: 🚀 Feature request
description: Submit a proposal/request for a new embedchain feature
body:
- type: textarea
attributes:
label: 🚀 The feature
description: >
A clear and concise description of the feature proposal
validations:
required: true
- type: textarea
attributes:
label: Motivation, pitch
description: >
Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too.
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: >
A description of any alternative solutions or features you've considered, if any.
- type: textarea
attributes:
label: Additional context
description: >
Add any other context or screenshots about the feature request.
- type: markdown
attributes:
value: >
Thanks for contributing 🎉!
+41
View File
@@ -0,0 +1,41 @@
## Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Refactor (does not change functionality, e.g. code style improvements, linting)
- [ ] Documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
Please delete options that are not relevant.
- [ ] Unit Test
- [ ] Test Script (please provide)
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
- [ ] I have checked my code and corrected any misspellings
## Maintainer Checklist
- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] Made sure Checks passed
+24
View File
@@ -0,0 +1,24 @@
name: cd
on:
release:
types:
- published
permissions:
id-token: write
contents: read
jobs:
publish_to_pypi:
name: publish to pypi on new release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: JRubics/poetry-publish@v1.16
name: Build and publish to PyPI
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}
ignore_dev_requirements: "yes"
repository_url: https://upload.pypi.org/legacy/
repository_name: embedchain
+28
View File
@@ -0,0 +1,28 @@
name: ci
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: pip install poetry==1.4.2
- name: Install dependencies
run: poetry install --all-extras
- name: Lint with ruff
run: make ci_lint
- name: Test with pytest
run: make ci_test
+5 -1
View File
@@ -166,4 +166,8 @@ cython_debug/
# Database
db
.vscode
.vscode
/poetry.lock
.idea/
.DS_Store
+20
View File
@@ -0,0 +1,20 @@
repos:
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.220'
hooks:
- id: ruff
name: ruff
# Respect `exclude` and `extend-exclude` settings.
args: ["--force-exclude"]
- repo: local
hooks:
- id: pytest-check
name: pytest-check
entry: poetry run pytest
language: system
pass_filenames: false
always_run: true
+74
View File
@@ -0,0 +1,74 @@
# Contributing to embedchain
Let us make contributing easy, collaborative and fun.
## Submit your Contribution through PR
To make a contribution, follow the following steps:
1. Fork and clone this repository
2. Do the changes on your fork with dedicated feature branch `feature/f1`
3. If you modified the code (new feature or bug-fix), please add tests for it
4. Include proper documentation / docstring and examples to run the feature
5. Check the linting
6. Ensure that all tests pass
7. Submit a pull request
For more details about pull requests, please read [GitHub's guides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
### 📦 Package manager
We use `poetry` as our package manager. You can install poetry by following the instructions [here](https://python-poetry.org/docs/#installation).
Please DO NOT use pip or conda to install the dependencies. Instead, use poetry:
```bash
poetry install --all-extras
or
poetry install --with dev
#activate
poetry shell
```
### 📌 Pre-commit
To ensure our standards, make sure to install pre-commit before star to contribute.
```bash
pre-commit install
```
### 🧹 Linting
We use `ruff` to lint our code. You can run the linter by running the following command:
```bash
make lint
```
Make sure that the linter does not report any errors or warnings before submitting a pull request.
### Code Format with `black`
We use `black` to reformat the code by running the following command:
```bash
make format
```
### 🧪 Testing
We use `pytest` to test our code. You can run the tests by running the following command:
```bash
poetry run pytest
```
Make sure that all tests pass before submitting a pull request.
## 🚀 Release Process
At the moment, the release process is manual. We try to make frequent releases. Usually, we release a new version when we have a new feature or bugfix. A developer with admin rights to the repository will create a new release on GitHub, and then publish the new version to PyPI.
+7 -1
View File
@@ -4,7 +4,7 @@ PIP := $(PYTHON) -m pip
PROJECT_NAME := embedchain
# Targets
.PHONY: install format lint clean test
.PHONY: install format lint clean test ci_lint ci_test
install:
$(PIP) install --upgrade pip
@@ -22,3 +22,9 @@ clean:
test:
$(PYTHON) -m pytest
ci_lint:
poetry run ruff .
ci_test:
poetry run pytest
+41 -582
View File
@@ -1,623 +1,82 @@
# embedchain
[![PyPI](https://img.shields.io/pypi/v/embedchain)](https://pypi.org/project/embedchain/)
[![Discord](https://dcbadge.vercel.app/api/server/nhvCbCtKV?style=flat)](https://discord.gg/6PzXDgEjG5)
[![Discord](https://dcbadge.vercel.app/api/server/6PzXDgEjG5?style=flat)](https://discord.gg/6PzXDgEjG5)
[![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)
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)
# Table of Contents
- [Latest Updates](#latest-updates)
- [What is embedchain?](#what-is-embedchain)
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Usage](#usage)
- [App Types](#app-types)
- [1. App (uses OpenAI models, paid)](#1-app-uses-openai-models-paid)
- [2. OpenSourceApp (uses opensource models, free)](#2-opensourceapp-uses-opensource-models-free)
- [3. PersonApp (uses OpenAI models, paid)](#3-personapp-uses-openai-models-paid)
- [Add Dataset](#add-dataset)
- [Interface Types](#interface-types)
- [Query Interface](#query-interface)
- [Chat Interface](#chat-interface)
- [Format supported](#format-supported)
- [Youtube Video](#youtube-video)
- [PDF File](#pdf-file)
- [Web Page](#web-page)
- [Doc File](#doc-file)
- [Text](#text)
- [QnA Pair](#qna-pair)
- [Reusing a Vector DB](#reusing-a-vector-db)
- [More Formats coming soon](#more-formats-coming-soon)
- [Testing](#testing)
- [Advanced](#advanced)
- [Configuration](#configuration)
- [Example](#example)
- [Configs](#configs)
- [InitConfig](#initconfig)
- [Add Config](#add-config)
- [Query Config](#query-config)
- [Chat Config](#chat-config)
- [Other methods](#other-methods)
- [Reset](#reset)
- [Count](#count)
- [How does it work?](#how-does-it-work)
- [Contribution Guidelines](#contribution-guidelines)
- [Tech Stack](#tech-stack)
- [Team](#team)
- [Author](#author)
- [Maintainer](#maintainer)
- [Citation](#citation)
# Latest Updates
- Introduce a new interface called `chat`. It remembers the history (last 5 messages) and can be used to powerful stateful bots. You can use it by calling `.chat` on any app instance. Works for both OpenAI and OpenSourceApp.
- Introduce a new app type called `OpenSourceApp`. It uses `gpt4all` as the LLM and `sentence transformers` all-MiniLM-L6-v2 as the embedding model. If you use this app, you dont have to pay for anything.
# What is embedchain?
Embedchain abstracts the entire process of loading a dataset, chunking it, creating embeddings and then storing in a vector database.
You can add a single or multiple dataset using `.add` and `.add_local` 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 1 youtube video, 1 book as pdf and 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 videos, pdf and blog posts and the QnA pair and embedchain will create a bot for you.
```python
from embedchain import App
naval_chat_bot = App()
# Embed Online Resources
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("web_page", "https://nav.al/feedback")
naval_chat_bot.add("web_page", "https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
naval_chat_bot.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.
```
# Getting Started
## Installation
First make sure that you have the package installed. If not, then install it using `pip`
## 🔧 Quick install
```bash
pip install embedchain
```
## Usage
## 🔥 Latest
Creating a chatbot involves 3 steps:
- **[2023/07/19]** Released support for 🦙 `llama2` model. Start creating your `llama2` based bots like this:
- Import the App instance (App Types)
- Add Dataset (Add Dataset)
- Query or Chat on the dataset and get answers (Interface Types)
```python
import os
### App Types
from embedchain import Llama2App
We have three types of App.
os.environ['REPLICATE_API_TOKEN'] = "REPLICATE API TOKEN"
#### 1. App (uses OpenAI models, paid)
zuck_bot = Llama2App()
```python
from embedchain import App
# Embed your data
zuck_bot.add("youtube_video", "https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
zuck_bot.add("web_page", "https://en.wikipedia.org/wiki/Mark_Zuckerberg")
naval_chat_bot = App()
```
# 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.
```
- `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).
## 🔍 Demo
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
Try out embedchain in your browser:
```python
import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
[![Open in Colab](https://camo.githubusercontent.com/84f0493939e0c4de4e6dbe113251b4bfb5353e57134ffd9fcab6b8714514d4d1/68747470733a2f2f636f6c61622e72657365617263682e676f6f676c652e636f6d2f6173736574732f636f6c61622d62616467652e737667)](https://colab.research.google.com/drive/138lMWhENGeEu7Q1-6lNbNTHGLZXBBz_B?usp=sharing)
#### 2. OpenSourceApp (uses opensource models, free)
## 📖 Documentation
```python
from embedchain import OpenSourceApp
The documentation for embedchain can be found at [docs.embedchain.ai](https://docs.embedchain.ai).
naval_chat_bot = OpenSourceApp()
```
## 💻 Usage
- `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.
Embedchain empowers you to create chatbot models similar to ChatGPT, using your own evolving dataset.
- Here there is no need to setup any api keys. You just need to install embedchain package and these will get automatically installed.
### Queries
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app.
#### 3. PersonApp (uses OpenAI models, paid)
```python
from embedchain import PersonApp
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).
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
```python
import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
### Add Dataset
- This step assumes that you have already created an `app` instance by either using `App` or `OpenSourceApp`. We are calling our app instance as `naval_chat_bot`
- Now use `.add` function to add any dataset.
```python
# naval_chat_bot = App() or
# naval_chat_bot = OpenSourceApp()
# Embed Online Resources
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("web_page", "https://nav.al/feedback")
naval_chat_bot.add("web_page", "https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
```
- 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
```
## Interface Types
### Query Interface
- This interface is like a question answering bot. It takes a question and gets the answer. It does not maintain context about the previous chats.
- To use this, call `.query` function to get the answer for any query.
```python
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
### Chat Interface
- This interface is chat interface where it remembers previous conversation. Right now it remembers 5 conversation by default.
- To use this, call `.chat` function to get the answer for any query.
```python
print(naval_chat_bot.chat("How to be happy in life?"))
# answer: The most important trick to being happy is to realize happiness is a skill you develop and a choice you make. You choose to be happy, and then you work at it. It's just like building muscles or succeeding at your job. It's about recognizing the abundance and gifts around you at all times.
print(naval_chat_bot.chat("who is naval ravikant?"))
# answer: Naval Ravikant is an Indian-American entrepreneur and investor.
print(naval_chat_bot.chat("what did the author say about happiness?"))
# answer: The author, Naval Ravikant, believes that happiness is a choice you make and a skill you develop. He compares the mind to the body, stating that just as the body can be molded and changed, so can the mind. He emphasizes the importance of being present in the moment and not getting caught up in regrets of the past or worries about the future. By being present and grateful for where you are, you can experience true happiness.
```
### Stream Response
- You can add config to your query method to stream responses like ChatGPT does. You would require a downstream handler to render the chunk in your desirable format. Supports both OpenAI model and OpenSourceApp.
- To use this, instantiate a `QueryConfig` or `ChatConfig` object with `stream=True`. Then pass it to the `.chat()` or `.query()` method. The following example iterates through the chunks and prints them as they appear.
```python
app = App()
query_config = QueryConfig(stream = True)
resp = app.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config)
for chunk in resp:
print(chunk, end="", flush=True)
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
## Format supported
We support the following formats:
### Youtube Video
To add any youtube video to your app, use the data_type (first argument to `.add`) as `youtube_video`. Eg:
```python
app.add('youtube_video', 'a_valid_youtube_url_here')
```
### PDF File
To add any pdf file, use the data_type as `pdf_file`. Eg:
```python
app.add('pdf_file', 'a_valid_url_where_pdf_file_can_be_accessed')
```
Note that we do not support password protected pdfs.
### Web Page
To add any web page, use the data_type as `web_page`. Eg:
```python
app.add('web_page', 'a_valid_web_page_url')
```
### Doc File
To add any doc/docx file, use the data_type as `docx`. Eg:
```python
app.add('docx', 'a_local_docx_file_path')
```
### Text
To supply your own text, use the data_type as `text` and enter a string. The text is not processed, this can be very versatile. Eg:
```python
app.add_local('text', 'Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.')
```
Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit.
### QnA Pair
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```python
app.add_local('qna_pair', ("Question", "Answer"))
```
### Sitemap
To add a XML site map containing list of all urls, use the data_type as `sitemap` and enter the sitemap url. Eg:
```python
app.add('sitemap', 'a_valid_sitemap_url/sitemap.xml')
```
### Reusing a Vector DB
Default behavior is to create a persistent vector DB in the directory **./db**. You can split your application into two Python scripts: one to create a local vector DB and the other to reuse this local persistent vector DB. This is useful when you want to index hundreds of documents and separately implement a chat interface.
Create a local index:
```python
from embedchain import App
naval_chat_bot = App()
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
```
You can reuse the local index with the same code, but without adding new documents:
```python
from embedchain import App
naval_chat_bot = App()
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
```
### More Formats coming soon
- 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.
## 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 `dry_run` method.
Following the example above, add this to your script:
```python
print(naval_chat_bot.dry_run('Can you tell me who Naval Ravikant is?'))
'''
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.
Q: Who is Naval Ravikant?
A: Naval Ravikant is an Indian-American entrepreneur and investor.
Query: Can you tell me who Naval Ravikant is?
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.**
## Colab Notebook and Video Tutorials
Chinese Colab Tutorial:https://colab.research.google.com/drive/10_7Y0x4YXWVjuhhYwVraGQLpKAatTQTm?usp=sharing
Chinese Video Tutorial:https://www.bilibili.com/video/BV1YX4y1H7oN
# Advanced
## Configuration
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.
### Example
Here's the readme example with configuration options.
For example, you can use Embedchain to create an Elon Musk bot using the following code:
```python
import os
from embedchain import App
from embedchain.config import InitConfig, AddConfig, QueryConfig
from chromadb.utils import embedding_functions
# Example: use your own embedding function
config = InitConfig(ef=embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name="text-embedding-ada-002"
))
naval_chat_bot = App(config)
# Create a bot instance
os.environ["OPENAI_API_KEY"] = "YOUR API KEY"
elon_bot = App()
# Example: define your own chunker config for `youtube_video`
youtube_add_config = {
"chunker": {
"chunk_size": 1000,
"chunk_overlap": 100,
"length_function": len,
}
}
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44", AddConfig(**youtube_add_config))
# Embed online resources
elon_bot.add("web_page", "https://en.wikipedia.org/wiki/Elon_Musk")
elon_bot.add("web_page", "https://tesla.com/elon-musk")
elon_bot.add("youtube_video", "https://www.youtube.com/watch?v=MxZpaJK74Y4")
add_config = AddConfig()
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf", add_config)
naval_chat_bot.add("web_page", "https://nav.al/feedback", add_config)
naval_chat_bot.add("web_page", "https://nav.al/agi", add_config)
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."), add_config)
query_config = QueryConfig() # Currently no options
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config))
# 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
```
Here's the example of using custom prompt template with `.query`
## 🤝 Contributing
```python
from embedchain.config import QueryConfig
from embedchain.embedchain import App
from string import Template
import wikipedia
einstein_chat_bot = App()
# Embed Wikipedia page
page = wikipedia.page("Albert Einstein")
einstein_chat_bot.add("text", page.content)
# Example: use your own custom template with `$context` and `$query`
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.
Use the following information about Albert Einstein to respond to
the human's query acting as Albert Einstein.
Context: $context
Keep the response brief. If you don't know the answer, just say that you don't know, don't try to make up an answer.
Human: $query
Albert Einstein:""")
query_config = QueryConfig(einstein_chat_template)
queries = [
"Where did you complete your studies?",
"Why did you win nobel prize?",
"Why did you divorce your first wife?",
]
for query in queries:
response = einstein_chat_bot.query(query, query_config)
print("Query: ", query)
print("Response: ", response)
# Output
# Query: Where did you complete your studies?
# Response: I completed my secondary education at the Argovian cantonal school in Aarau, Switzerland.
# Query: Why did you win nobel prize?
# Response: I won the Nobel Prize in Physics in 1921 for my services to Theoretical Physics, particularly for my discovery of the law of the photoelectric effect.
# Query: Why did you divorce your first wife?
# Response: We divorced due to living apart for five years.
```
**Client Mode**. By defining a (ChromaDB) server, you can run EmbedChain as a client only.
```python
from embedchain import App
config = InitConfig(host="localhost", port="8080")
app = App(config)
```
This is useful for scalability. Say you have EmbedChain behind an API with multiple workers. If you separate clients and server, all clients can connect to the server, which only has to keep one instance of the database in memory. You also don't have to worry about replication.
To run a chroma db server, run `git clone https://github.com/chroma-core/chroma.git`, navigate to the directory (`cd chroma`) and then start the server with `docker-compose up -d --build`.
### Configs
This section describes all possible config options.
#### **InitConfig**
|option|description|type|default|
|---|---|---|---|
|log_level|log level|string|WARNING|
|ef|embedding function|chromadb.utils.embedding_functions|{text-embedding-ada-002}|
|db|vector database (experimental)|BaseVectorDB|ChromaDB|
|host|hostname for (Chroma) DB server|string|None|
|port|port number for (Chroma) DB server|string, int|None|
#### **Add Config**
|option|description|type|default|
|---|---|---|---|
|chunker|chunker config|ChunkerConfig|Default values for chunker depends on the `data_type`. Please refer [ChunkerConfig](#chunker-config)|
|loader|loader config|LoaderConfig|None|
##### **Chunker Config**
|option|description|type|default|
|---|---|---|---|
|chunk_size|Maximum size of chunks to return|int|Default value for various `data_type` mentioned below|
|chunk_overlap|Overlap in characters between chunks|int|Default value for various `data_type` mentioned below|
|length_function|Function that measures the length of given chunks|typing.Callable|Default value for various `data_type` mentioned below|
Default values of chunker config parameters for different `data_type`:
|data_type|chunk_size|chunk_overlap|length_function|
|---|---|---|---|
|docx|1000|0|len|
|text|300|0|len|
|qna_pair|300|0|len|
|web_page|500|0|len|
|pdf_file|1000|0|len|
|youtube_video|2000|0|len|
##### **Loader Config**
_coming soon_
#### **Query Config**
|option|description|type|default|
|---|---|---|---|
|number_documents|number of documents to be retrieved as context|int|1|
|template|custom template for prompt|Template|Template("Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. \$context Query: $query Helpful Answer:")|
|history|include conversation history from your client or database|any (recommendation: list[str])|None
|stream|control if response is streamed back to the user|bool|False|
|model|OpenAI model|string|gpt-3.5-turbo-0613|
|temperature|creativity of the model (0-1)|float|0|
|max_tokens|limit maximum tokens used|int|1000|
|top_p|diversity of words used by the model (0-1)|float|1|
#### **Chat Config**
All options for query and...
_coming soon_
history is handled automatically, the config option is not supported.
## Other methods
### Reset
Resets the database and deletes all embeddings. Irreversible. Requires reinitialization afterwards.
```python
app.reset()
```
### Count
Counts the number of embeddings (chunks) in the database.
```python
print(app.count())
# returns: 481
```
# 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.
# Contribution Guidelines
Thank you for your interest in contributing to the EmbedChain project! We welcome your ideas and contributions to help improve the project. Please follow the instructions below to get started:
1. **Fork the repository**: Click on the "Fork" button at the top right corner of this repository page. This will create a copy of the repository in your own GitHub account.
2. **Install the required dependencies**: Ensure that you have the necessary dependencies installed in your Python environment. You can do this by running the following command:
```bash
make install
```
3. **Make changes in the code**: Create a new branch in your forked repository and make your desired changes in the codebase.
4. **Format code**: Before creating a pull request, it's important to ensure that your code follows our formatting guidelines. Run the following commands to format the code:
```bash
make lint format
```
5. **Create a pull request**: When you are ready to contribute your changes, submit a pull request to the EmbedChain repository. Provide a clear and descriptive title for your pull request, along with a detailed description of the changes you have made.
# 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
- [gpt4all](https://github.com/nomic-ai/gpt4all) as an open source LLM
- [sentence-transformers](https://huggingface.co/sentence-transformers) as open source embedding model
# Team
## Author
- Taranjeet Singh ([@taranjeetio](https://twitter.com/taranjeetio))
## Maintainer
- [cachho](https://github.com/cachho)
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).
## Citation
@@ -626,7 +85,7 @@ If you utilize this repository, please consider citing it with:
```
@misc{embedchain,
author = {Taranjeet Singh},
title = {Embechain: Framework to easily create LLM powered bots over any dataset},
title = {Embedchain: Framework to easily create LLM powered bots over any dataset},
year = {2023},
publisher = {GitHub},
journal = {GitHub repository},
+25
View File
@@ -0,0 +1,25 @@
# Contributing to embedchain docs
### 👩‍💻 Development
Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
```
npm i -g mintlify
```
Run the following command at the root of your documentation (where mint.json is)
```
mintlify dev
```
### 😎 Publishing Changes
Changes will be deployed to production automatically after your PR is merged to the main branch.
#### Troubleshooting
- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
- Page loads as a 404 - Make sure you are running in a folder with `mint.json`
+25
View File
@@ -0,0 +1,25 @@
---
title: '➕ Adding Data'
---
## Add Dataset
- This step assumes that you have already created an `app` instance by either using `App`, `OpenSourceApp` or `CustomApp`. We are calling our app instance as `naval_chat_bot` 🤖
- Now use `.add()` function to add any dataset.
```python
# naval_chat_bot = App() or
# naval_chat_bot = OpenSourceApp()
# Embed Online Resources
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("web_page", "https://nav.al/feedback")
naval_chat_bot.add("web_page", "https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
```
The possible formats to add data can be found on the [Supported Data Formats](/advanced/data_types) page.
+127
View File
@@ -0,0 +1,127 @@
---
title: '📱 App types'
---
## App Types
We have three types of App.
### App
```python
from embedchain import App
app = App()
```
- `App` uses OpenAI's model, so these are paid models. 💸 You will be charged for embedding model usage and LLM usage.
- `App` uses OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you have don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- `App` is opinionated. It uses the best embedding model and LLM on the market.
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
```python
import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
### Llama2App
```python
import os
from embedchain import Llama2App
os.environ['REPLICATE_API_TOKEN'] = "REPLICATE API TOKEN"
zuck_bot = Llama2App()
# Embed your data
zuck_bot.add("youtube_video", "https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
zuck_bot.add("web_page", "https://en.wikipedia.org/wiki/Mark_Zuckerberg")
# Nice, your bot is ready now. Start asking questions to your bot.
zuck_bot.query("Who is Mark Zuckerberg?")
# Answer: Mark Zuckerberg is an American internet entrepreneur and business magnate. He is the co-founder and CEO of Facebook. Born in 1984, he dropped out of Harvard University to focus on his social media platform, which has since grown to become one of the largest and most influential technology companies in the world.
# Enable web search for your bot
zuck_bot.online = True # enable internet access for the bot
zuck_bot.query("Who owns the new threads app and when it was founded?")
# Answer: Based on the context provided, the new Threads app is owned by Meta, the parent company of Facebook, Instagram, and WhatsApp.
```
- `Llama2App` uses Replicate's LLM model, so these are paid models. You can get the `REPLICATE_API_TOKEN` by registering on [their website](https://replicate.com/account).
- `Llama2App` uses OpenAI's embedding model to create embeddings for chunks. Make sure that you have an OpenAI account and an API key. If you have don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
### OpenSourceApp
```python
from embedchain import OpenSourceApp
app = OpenSourceApp()
```
- `OpenSourceApp` uses open source embedding and LLM model. It uses `all-MiniLM-L6-v2` from Sentence Transformers library as the embedding model and `gpt4all` as the LLM.
- Here there is no need to setup any api keys. You just need to install embedchain package and these will get automatically installed. 📦
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app. 📚
- `OpenSourceApp` is opinionated. It uses the best open source embedding model and LLM on the market.
### CustomApp
```python
from embedchain import CustomApp
from embedchain.config import CustomAppConfig
from embedchain.models import Providers, EmbeddingFunctions
config = CustomAppConfig(embedding_fn=EmbeddingFunctions.OPENAI, provider=Providers.OPENAI)
app = CustomApp(config)
```
- `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
### PersonApp
```python
from embedchain import PersonApp
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).
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
```python
import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
#### 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
```
+89
View File
@@ -0,0 +1,89 @@
---
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.
## Examples
### General
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
# 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)
# Example: define your own chunker config for `youtube_video`
chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=100, length_function=len)
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44", AddConfig(chunker=chunker_config))
add_config = AddConfig()
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf", add_config)
naval_chat_bot.add("web_page", "https://nav.al/feedback", add_config)
naval_chat_bot.add("web_page", "https://nav.al/agi", add_config)
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."), add_config)
query_config = QueryConfig()
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config))
```
### Custom prompt template
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
einstein_chat_bot = App()
# Embed Wikipedia page
page = wikipedia.page("Albert Einstein")
einstein_chat_bot.add("text", page.content)
# Example: use your own custom template with `$context` and `$query`
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.
Use the following information about Albert Einstein to respond to
the human's query acting as Albert Einstein.
Context: $context
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)
queries = [
"Where did you complete your studies?",
"Why did you win nobel prize?",
"Why did you divorce your first wife?",
]
for query in queries:
response = einstein_chat_bot.query(query, query_config)
print("Query: ", query)
print("Response: ", response)
# Output
# Query: Where did you complete your studies?
# Response: I completed my secondary education at the Argovian cantonal school in Aarau, Switzerland.
# Query: Why did you win nobel prize?
# Response: I won the Nobel Prize in Physics in 1921 for my services to Theoretical Physics, particularly for my discovery of the law of the photoelectric effect.
# Query: Why did you divorce your first wife?
# Response: We divorced due to living apart for five years.
```
+112
View File
@@ -0,0 +1,112 @@
---
title: '📋 Supported data formats'
---
Embedchain supports following data formats:
### Youtube video
To add any youtube video to your app, use the data_type (first argument to `.add()` method) as `youtube_video`. Eg:
```python
app.add('youtube_video', 'a_valid_youtube_url_here')
```
### PDF file
To add any pdf file, use the data_type as `pdf_file`. Eg:
```python
app.add('pdf_file', 'a_valid_url_where_pdf_file_can_be_accessed')
```
Note that we do not support password protected pdfs.
### Web page
To add any web page, use the data_type as `web_page`. Eg:
```python
app.add('web_page', 'a_valid_web_page_url')
```
### Sitemap
Add all web pages from an xml-sitemap. Filters non-text files. Use the data_type as `sitemap`. Eg:
```python
app.add('sitemap', 'https://example.com/sitemap.xml')
```
### Doc file
To add any doc/docx file, use the data_type as `docx`. Eg:
```python
app.add('docx', 'a_local_docx_file_path')
```
### Code documentation website loader
To add any code documentation website as a loader, use the data_type as `docs_site`. Eg:
```python
app.add("docs_site", "https://docs.embedchain.ai/")
```
### Notion
To use notion you must install the extra dependencies with `pip install embedchain[notion]`.
To load a notion page, use the data_type as `notion`.
The next argument must **end** with the `notion page id`. The id is a 32-character string. Eg:
```python
app.add("notion", "cfbc134ca6464fc980d0391613959196")
app.add("notion", "my-page-cfbc134ca6464fc980d0391613959196")
app.add("notion", "https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196")
```
### Text
To supply your own text, use the data_type as `text` and enter a string. The text is not processed, this can be very versatile. Eg:
```python
app.add_local('text', 'Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.')
```
Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit.
### QnA pair
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```python
app.add_local('qna_pair', ("Question", "Answer"))
```
## Reusing a vector database
Default behavior is to create a persistent vector DB in the directory **./db**. You can split your application into two Python scripts: one to create a local vector DB and the other to reuse this local persistent vector DB. This is useful when you want to index hundreds of documents and separately implement a chat interface.
Create a local index:
```python
from embedchain import App
naval_chat_bot = App()
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
```
You can reuse the local index with the same code, but without adding new documents:
```python
from embedchain import App
naval_chat_bot = App()
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
```
### More formats (coming soon!)
- If you want to add any other format, please create an [issue](https://github.com/embedchain/embedchain/issues) and we will add it to the list of supported formats.
+75
View File
@@ -0,0 +1,75 @@
---
title: '🤝 Interface types'
---
## Interface Types
The embedchain app exposes the following methods.
### Query Interface
- This interface is like a question answering bot. It takes a question and gets the answer. It does not maintain context about the previous chats.❓
- To use this, call `.query()` function to get the answer for any query.
```python
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
### Chat Interface
- This interface is chat interface where it remembers previous conversation. Right now it remembers 5 conversation by default. 💬
- To use this, call `.chat` function to get the answer for any query.
```python
print(naval_chat_bot.chat("How to be happy in life?"))
# answer: The most important trick to being happy is to realize happiness is a skill you develop and a choice you make. You choose to be happy, and then you work at it. It's just like building muscles or succeeding at your job. It's about recognizing the abundance and gifts around you at all times.
print(naval_chat_bot.chat("who is naval ravikant?"))
# answer: Naval Ravikant is an Indian-American entrepreneur and investor.
print(naval_chat_bot.chat("what did the author say about happiness?"))
# answer: The author, Naval Ravikant, believes that happiness is a choice you make and a skill you develop. He compares the mind to the body, stating that just as the body can be molded and changed, so can the mind. He emphasizes the importance of being present in the moment and not getting caught up in regrets of the past or worries about the future. By being present and grateful for where you are, you can experience true happiness.
```
#### Dry Run
Dry Run is an option in the `query` and `chat` methods that allows the user to not send their constructed prompt to the LLM, to save money. It's used for [testing](/advanced/testing#dry-run).
### Stream Response
- You can add config to your query method to stream responses like ChatGPT does. You would require a downstream handler to render the chunk in your desirable format. Supports both OpenAI model and OpenSourceApp. 📊
- To use this, instantiate a `QueryConfig` or `ChatConfig` object with `stream=True`. Then pass it to the `.chat()` or `.query()` method. The following example iterates through the chunks and prints them as they appear.
```python
app = App()
query_config = QueryConfig(stream = True)
resp = app.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config)
for chunk in resp:
print(chunk, end="", flush=True)
# answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
```
### Other Methods
#### Reset
Resets the database and deletes all embeddings. Irreversible. Requires reinitialization afterwards.
```python
app.reset()
```
#### Count
Counts the number of embeddings (chunks) in the database.
```python
print(app.count())
# returns: 481
```
+74
View File
@@ -0,0 +1,74 @@
---
title: '🔍 Query configurations'
---
## AppConfig
| option | description | type | default |
|-----------|-----------------------|---------------------------------|------------------------|
| log_level | log level | string | WARNING |
| embedding_fn| embedding function | chromadb.utils.embedding_functions | \{text-embedding-ada-002\} |
| db | vector database (experimental) | BaseVectorDB | ChromaDB |
| collection_name | initial collection name for the database | string | embedchain_store |
## AddConfig
|option|description|type|default|
|---|---|---|---|
|chunker|chunker config|ChunkerConfig|Default values for chunker depends on the `data_type`. Please refer [ChunkerConfig](#chunker-config)|
|loader|loader config|LoaderConfig|None|
Yes, you are passing `ChunkerConfig` to `AddConfig`, like so:
```python
chunker_config = ChunkerConfig(chunk_size=100)
add_config = AddConfig(chunker=chunker_config)
app.add_local("text", "lorem ipsum", config=add_config)
```
### ChunkerConfig
|option|description|type|default|
|---|---|---|---|
|chunk_size|Maximum size of chunks to return|int|Default value for various `data_type` mentioned below|
|chunk_overlap|Overlap in characters between chunks|int|Default value for various `data_type` mentioned below|
|length_function|Function that measures the length of given chunks|typing.Callable|Default value for various `data_type` mentioned below|
Default values of chunker config parameters for different `data_type`:
|data_type|chunk_size|chunk_overlap|length_function|
|---|---|---|---|
|docx|1000|0|len|
|text|300|0|len|
|qna_pair|300|0|len|
|web_page|500|0|len|
|pdf_file|1000|0|len|
|youtube_video|2000|0|len|
|docs_site|500|50|len|
|notion|300|0|len|
### LoaderConfig
_coming soon_
## QueryConfig
|option|description|type|default|
|---|---|---|---|
|number_documents|Absolute number of documents to pull from the database as context.|int|1
|template|custom template for prompt. If history is used with query, $history has to be included as well.|Template|Template("Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. \$context Query: \$query Helpful Answer:")|
|model|name of the model used.|string|depends on app type|
|temperature|Controls the randomness of the model's output. Higher values (closer to 1) make output more random, lower values make it more deterministic.|float|0|
|max_tokens|Controls how many tokens are used. Exact implementation (whether it counts prompt and/or response) depends on the model.|int|1000|
|top_p|Controls the diversity of words. Higher values (closer to 1) make word selection more diverse, lower values make words less diverse.|float|1|
|history|include conversation history from your client or database.|any (recommendation: list[str])|None|
|stream|control if response is streamed back to the user.|bool|False|
## ChatConfig
All options for query and...
_coming soon_
`history` is not supported, as that is handled is handled automatically, the config option is not supported.
+83
View File
@@ -0,0 +1,83 @@
---
title: '🎪 Community showcase'
---
Embedchain community has been super active in creating demos on top of Embedchain. On this page, we showcase all the apps, blogs, videos, and tutorials created by the community. ❤️
## Apps
### Open Source
- [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/))
- [LLM bot trained on own messages](https://github.com/Harin329/harinBot) by Hao Wu
### Closed Source
- [Taobot.io](https://taobot.io) - chatbot & knowledgebase hybrid by [cachho](https://github.com/cachho)
- [Create Instant ChatBot 🤖 using embedchain](https://databutton.com/v/h3e680h9) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1674704745154641920/))
- [JOBO 🤖 — The AI-driven sidekick to craft your resume](https://try-jobo.com/) by Enrico Willemse, ([LinkedIn Post](https://www.linkedin.com/posts/enrico-willemse_jobai-gptfun-embedchain-activity-7090340080879374336-ueLB/))
- [Explore Your Knowledge Base: Interactive chats over various forms of documents](https://chatdocs.dkedar.com/) by Kedar Dabhadkar, ([LinkedIn Post](https://www.linkedin.com/posts/dkedar7_machinelearning-llmops-activity-7092524836639424513-2O3L/))
## Templates
### Replit
- [Embedchain Chat Bot](https://replit.com/@taranjeet1/Embedchain-Chat-Bot) by taranjeetio
- [Embedchain Memory Chat Bot Template](https://replit.com/@taranjeetio/Embedchain-Memory-Chat-Bot-Template) by taranjeetio
- [Chatbot app to demonstrate question-answering using retrieved information](https://replit.com/@AllisonMorrell/EmbedChainlitPublic) by Allison Morrell, ([LinkedIn Post](https://www.linkedin.com/posts/allison-morrell-2889275a_retrievalbot-screenshots-activity-7080339991754649600-wihZ/))
## Posts
### Blogs
- [Customer Service LINE Bot](https://www.evanlin.com/langchain-embedchain/) by Evan Lin
- [Chatbot in Under 5 mins using Embedchain](https://medium.com/@ayush.wattal/chatbot-in-under-5-mins-using-embedchain-a4f161fcf9c5) by Ayush Wattal
- [Understanding what the LLM framework embedchain does](https://zenn.dev/hijikix/articles/4bc8d60156a436) by Daisuke Hashimoto
- [In bed with GPT and Node.js](https://dev.to/worldlinetech/in-bed-with-gpt-and-nodejs-4kh2) by Raphaël Semeteys, ([LinkedIn Post](https://www.linkedin.com/posts/raphaelsemeteys_in-bed-with-gpt-and-nodejs-activity-7088113552326029313-nn87/))
- [Using Embedchain — A powerful LangChain Python wrapper to build Chat Bots even faster!⚡](https://medium.com/@avra42/using-embedchain-a-powerful-langchain-python-wrapper-to-build-chat-bots-even-faster-35c12994a360) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1686767751560310784/))
### LinkedIn
- [What is embedchain](https://www.linkedin.com/posts/activity-7079393104423698432-wRyi/) by Rithesh Sreenivasan
- [Building a chatbot with EmbedChain](https://www.linkedin.com/posts/activity-7078434598984060928-Zdso/) by Lior Sinclair
- [Making chatbot without vs with embedchain](https://www.linkedin.com/posts/kalyanksnlp_llms-chatbots-langchain-activity-7077453416221863936-7N1L/) by Kalyan KS
- [EmbedChain - very intuitive, first you index your data and then query!](https://www.linkedin.com/posts/shubhamsaboo_embedchain-a-framework-to-easily-create-activity-7079535460699557888-ad1X/) by Shubham Saboo
- [EmbedChain - Harnessing power of LLM](https://www.linkedin.com/posts/uditsaini_chatbotrevolution-llmpoweredbots-embedchainframework-activity-7077520356827181056-FjTK/) by Udit S.
- [AI assistant for ABBYY Vantage](https://www.linkedin.com/posts/maximevermeir_llm-github-abbyy-activity-7081658972071424000-fXfZ/) by Maxime V.
- [About embedchain](https://www.linkedin.com/feed/update/urn:li:activity:7080984218914189312/) by Morris Lee
- [How to use Embedchain](https://www.linkedin.com/posts/nehaabansal_github-embedchainembedchain-framework-activity-7085830340136595456-kbW5/) by Neha Bansal
- [Youtube/Webpage summary for Energy Study](https://www.linkedin.com/posts/bar%C4%B1%C5%9F-sanl%C4%B1-34b82715_enerji-python-activity-7082735341563977730-Js0U/) by Barış Sanlı, ([Tweet](https://twitter.com/barissanli/status/1676968784979193857/))
### Twitter
- [What is embedchain](https://twitter.com/AlphaSignalAI/status/1672668574450847745) by Lior
- [Building a chatbot with Embedchain](https://twitter.com/Saboo_Shubham_/status/1673537044419686401) by Shubham Saboo
- [Chatbot docker image behind an API with yaml configs with Embedchain](https://twitter.com/tricalt/status/1678411430192730113/) by Vasilije
- [Build AI powered PDF chatbot with just five lines of Python code with Embedchain!](https://twitter.com/Saboo_Shubham_/status/1676627104866156544/) by Shubham Saboo
- [Chatbot against a youtube video using embedchain](https://twitter.com/smaameri/status/1675201443043704834/) by Sami Maameri
- [Highlights of EmbedChain](https://twitter.com/carl_AIwarts/status/1673542204328120321/) by carl_AIwarts
- [Build Llama-2 chatbot in less than 5 minutes](https://twitter.com/Saboo_Shubham_/status/1682168956918833152/) by Shubham Saboo
- [All cool features of embedchain](https://twitter.com/DhravyaShah/status/1683497882438217728/) by Dhravya Shah, ([LinkedIn Post](https://www.linkedin.com/posts/dhravyashah_what-if-i-tell-you-that-you-can-make-an-ai-activity-7089459599287726080-ZIYm/))
## Videos
- [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
- [Chat With Your PDFs in less than 10 lines of code! EMBEDCHAIN tutorial](https://www.youtube.com/watch?v=1ugkcsAcw44) by Phani Reddy
- [How To Create A Custom Knowledge AI Powered Bot | Install + How To Use](https://www.youtube.com/watch?v=VfCrIiAst-c) by The Ai Solopreneur
- [Build Custom Chatbot in 6 min with this Framework [Beginner Friendly]](https://www.youtube.com/watch?v=-8HxOpaFySM) by Maya Akim
- [embedchain-streamlit-app](https://www.youtube.com/watch?v=3-9GVd-3v74) by Amjad Raza
- [🤖CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN - a LangChain wrapper, in few lines of code !](https://www.youtube.com/watch?v=Mp7zJe4TIdM) by Avra
- [Building resource-driven LLM-powered bots with Embedchain](https://www.youtube.com/watch?v=IVfcAgxTO4I) by BugBytes
- [embedchain-streamlit-demo](https://www.youtube.com/watch?v=yJAWB13FhYQ) by Amjad Raza
- [Embedchain - create your own AI chatbots using open source models](https://www.youtube.com/shorts/O3rJWKwSrWE) by Dhravya Shah
- [AI ChatBot in 5 lines Python Code](https://www.youtube.com/watch?v=zjWvLJLksv8) by Data Engineering
- [Interview with Karl Marx](https://www.youtube.com/watch?v=5Y4Tscwj1xk) by Alexander Ray Williams
## Mentions
### Github repos
- [awesome-ChatGPT-repositories](https://github.com/taishi-i/awesome-ChatGPT-repositories)
+29
View File
@@ -0,0 +1,29 @@
---
title: '🧪 Testing'
---
## Methods for testing
### Dry Run
Before you consume valueable tokens, you should make sure that the embedding you have done works and that it's receiving the correct document from the database.
For this you can use the `dry_run` option in your `query` or `chat` method.
Following the example above, add this to your script:
```python
print(naval_chat_bot.query('Can you tell me who Naval Ravikant is?', dry_run=True))
'''
Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
Q: Who is Naval Ravikant?
A: Naval Ravikant is an Indian-American entrepreneur and investor.
Query: Can you tell me who Naval Ravikant is?
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.**
+34
View File
@@ -0,0 +1,34 @@
---
title: '💾 Vector Database'
---
We support `Chroma` and `Elasticsearch` as two vector database.
`Chroma` is used as a default database.
### Elasticsearch
In order to use `Elasticsearch` as vector database we need to use App type `CustomApp`.
```python
import os
from embedchain import CustomApp
from embedchain.config import CustomAppConfig, ElasticsearchDBConfig
from embedchain.models import Providers, EmbeddingFunctions, VectorDatabases
os.environ["OPENAI_API_KEY"] = 'OPENAI_API_KEY'
es_config = ElasticsearchDBConfig(
# elasticsearch url or list of nodes url with different hosts and ports.
es_url='http://localhost:9200',
# pass named parameters supported by Python Elasticsearch client
ca_certs="/path/to/http_ca.crt",
basic_auth=("username", "password")
)
config = CustomAppConfig(
embedding_fn=EmbeddingFunctions.OPENAI,
provider=Providers.OPENAI,
db_type=VectorDatabases.ELASTICSEARCH,
es_config=es_config,
)
es_app = CustomApp(config)
```
- Set `db_type=VectorDatabases.ELASTICSEARCH` and `es_config=ElasticsearchDBConfig(es_url='')` in `CustomAppConfig`.
- `ElasticsearchDBConfig` accepts `es_url` as elasticsearch url or as list of nodes url with different hosts and ports. Additionally we can pass named paramaters supported by Python Elasticsearch client.
+60
View File
@@ -0,0 +1,60 @@
---
title: '👨‍💻 Development'
description: 'Contribute to Embedchain framework development'
---
Thank you for your interest in contributing to the EmbedChain project! We welcome your ideas and contributions to help improve the project. Please follow the instructions below to get started:
1. **Fork the repository**: Click on the "Fork" button at the top right corner of this repository page. This will create a copy of the repository in your own GitHub account.
2. **Install the required dependencies**: Ensure that you have the necessary dependencies installed in your Python environment. You can do this by running the following command:
```bash
make install
```
3. **Make changes in the code**: Create a new branch in your forked repository and make your desired changes in the codebase.
4. **Format code**: Before creating a pull request, it's important to ensure that your code follows our formatting guidelines. Run the following commands to format the code:
```bash
make lint format
```
5. **Create a pull request**: When you are ready to contribute your changes, submit a pull request to the EmbedChain repository. Provide a clear and descriptive title for your pull request, along with a detailed description of the changes you have made.
# 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
- [gpt4all](https://github.com/nomic-ai/gpt4all) as an open source LLM
- [sentence-transformers](https://huggingface.co/sentence-transformers) as open source embedding model
## Team
### Author
- Taranjeet Singh ([@taranjeetio](https://twitter.com/taranjeetio))
### Maintainer
- Deshraj Yadav ([@deshrajdry](https://twitter.com/taranjeetio))
- [cachho](https://github.com/cachho)
### 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/embedchain}},
}
```
+61
View File
@@ -0,0 +1,61 @@
---
title: '📝 Documentation'
description: 'Contribute to Embedchain docs'
---
<Info>
**Prerequisite** You should have installed Node.js (version 18.10.0 or
higher).
</Info>
Step 1. Install Mintlify on your OS:
<CodeGroup>
```bash npm
npm i -g mintlify
```
```bash yarn
yarn global add mintlify
```
</CodeGroup>
Step 2. Go to the `docs/` directory (where you can find `mint.json`) and run the following command:
```bash
mintlify dev
```
The documentation website is now available at `http://localhost:3000`.
### Custom Ports
Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333:
```bash
mintlify dev --port 3333
```
You will see an error like this if you try to run Mintlify in a port that's already taken:
```md
Error: listen EADDRINUSE: address already in use :::3000
```
## Mintlify Versions
Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production.
<CodeGroup>
```bash npm
npm i -g mintlify@latest
```
```bash yarn
yarn global upgrade mintlify
```
</CodeGroup>
+98
View File
@@ -0,0 +1,98 @@
---
title: 'Development'
description: 'Learn how to preview changes locally'
---
<Info>
**Prerequisite** You should have installed Node.js (version 18.10.0 or
higher).
</Info>
Step 1. Install Mintlify on your OS:
<CodeGroup>
```bash npm
npm i -g mintlify
```
```bash yarn
yarn global add mintlify
```
</CodeGroup>
Step 2. Go to the docs are located (where you can find `mint.json`) and run the following command:
```bash
mintlify dev
```
The documentation website is now available at `http://localhost:3000`.
### Custom Ports
Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333:
```bash
mintlify dev --port 3333
```
You will see an error like this if you try to run Mintlify in a port that's already taken:
```md
Error: listen EADDRINUSE: address already in use :::3000
```
## Mintlify Versions
Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production.
<CodeGroup>
```bash npm
npm i -g mintlify@latest
```
```bash yarn
yarn global upgrade mintlify
```
</CodeGroup>
## Deployment
<Tip>
Unlimited editors available under the [Startup
Plan](https://mintlify.com/pricing)
</Tip>
You should see the following if the deploy successfully went through:
<Frame>
<img src="/images/checks-passed.png" style={{ borderRadius: '0.5rem' }} />
</Frame>
## Troubleshooting
Here's how to solve some common problems when working with the CLI.
<AccordionGroup>
<Accordion title="Mintlify is not loading">
Update to Node v18. Run `mintlify install` and try again.
</Accordion>
<Accordion title="No such file or directory on Windows">
Go to the `C:/Users/Username/.mintlify/` directory and remove the `mint`
folder. Then Open the Git Bash in this location and run `git clone
https://github.com/mintlify/mint.git`.
Repeat step 3.
</Accordion>
<Accordion title="Getting an unknown error">
Try navigating to the root of your device and delete the ~/.mintlify folder.
Then run `mintlify dev` again.
</Accordion>
</AccordionGroup>
Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/command-line)
+22
View File
@@ -0,0 +1,22 @@
---
title: '🌐 Full Stack'
---
### 🐳 Docker Setup
- To setup full stack app using docker, run the following command inside this folder using your terminal.
```bash
docker-compose up --build
```
📝 Note: The build command might take a while to install all the packages depending on your system resources.
### 🚀 Usage Instructions
- Go to [http://localhost:3000/](http://localhost:3000/) in your browser to view the dashboard.
- Add your `OpenAI API key` 🔑 in the Settings.
- Create a new bot and you'll be navigated to its page.
- Here you can add your data sources and then chat with the bot.
🎉 Happy Chatting! 🎉
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

+56
View File
@@ -0,0 +1,56 @@
---
title: 📚 Introduction
description: '📝 Embedchain is a framework to easily create LLM powered bots over any dataset.'
---
## 🤔 What is Embedchain?
Embedchain abstracts the entire process of loading a dataset, chunking it, creating embeddings, and storing it in a vector database.
You can add a single or multiple datasets using the .add and .add_local functions. Then, simply use the .query function to find answers from the added datasets.
If you want to create a Naval Ravikant bot with a YouTube video, a book in PDF format, two blog posts, and a question and answer pair, all you need to do is add the respective links. Embedchain will take care of the rest, creating a bot for you.
```python
from embedchain import App
naval_chat_bot = App()
# Embed Online Resources
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
naval_chat_bot.add("web_page", "https://nav.al/feedback")
naval_chat_bot.add("web_page", "https://nav.al/agi")
# Embed Local Resources
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
naval_chat_bot.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.
```
## 🚀 How it works?
Creating a chat bot over any dataset involves the following steps:
1. Load the data
2. Create meaningful chunks
3. Create embeddings for each chunk
4. Store the chunks in a vector database
When a user asks a query, the following process happens to find the answer:
1. Create an embedding for the query
2. Find similar documents for the query from the vector database
3. Pass the similar documents as context to LLM to get the final answer.
The process of loading the dataset and querying involves multiple steps, each with its own nuances:
- 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 a vector database? Which vector database should I use?
- Should I store metadata along with the embeddings?
- How should I find similar documents for a query? Which ranking model should I use?
Embedchain takes care of all these nuances and provides a simple interface to create bots over any dataset.
In the first release, we make it easier for anyone to get a chatbot over any dataset up and running in less than a minute. Just create an app instance, add the datasets using the `.add()` function, and use the `.query()` function to get the relevant answers.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

+54
View File
@@ -0,0 +1,54 @@
{
"$schema": "https://mintlify.com/schema.json",
"name": "Embedchain",
"logo": {
"dark": "/logo/dark.svg",
"light": "/logo/light.svg"
},
"favicon": "/favicon.png",
"colors": {
"primary": "#12A7D3",
"light": "#81D7F7",
"dark": "#004E7A"
},
"topbarLinks": [
{
"name": "Twitter",
"url": "https://twitter.com/embedchain"
},
{
"name": "Discord",
"url": "https://discord.gg/6PzXDgEjG5"
}
],
"topbarCtaButton": {
"name": "GitHub",
"url": "https://embedchain.ai"
},
"navigation": [
{
"group": "Getting started",
"pages": ["quickstart", "introduction"]
},
{
"group": "Advanced",
"pages": ["advanced/app_types", "advanced/interface_types", "advanced/adding_data","advanced/data_types", "advanced/query_configuration", "advanced/configuration", "advanced/testing", "advanced/vector_database", "advanced/showcase"]
},
{
"group": "Examples",
"pages": ["examples/full_stack"]
},
{
"group": "Contribution Guidelines",
"pages": ["contribution/dev", "contribution/docs"]
}
],
"footerSocials": {
"twitter": "https://twitter.com/embedchain",
"github": "https://github.com/embedchain/embedchain",
"linkedin": "https://www.linkedin.com/company/embedchain"
},
"backgroundImage": "/background.png",
"isWhiteLabeled": true
}
+35
View File
@@ -0,0 +1,35 @@
---
title: '🚀 Quickstart'
description: '💡 Start building LLM powered bots under 30 seconds'
---
Install embedchain python package:
```bash
pip install embedchain
```
Creating a chatbot involves 3 steps:
- ⚙️ Import the App instance
- 🗃️ Add Dataset
- 💬 Query or Chat on the dataset and get answers (Interface Types)
Run your first bot in python using the following code. Make sure to set the `OPENAI_API_KEY` 🔑 environment variable in the code.
```python
import os
from embedchain import App
os.environ["OPENAI_API_KEY"] = "xxx"
elon_musk_bot = App()
# Embed Online Resources
elon_musk_bot.add("web_page", "https://en.wikipedia.org/wiki/Elon_Musk")
elon_musk_bot.add("web_page", "https://www.tesla.com/elon-musk")
response = elon_musk_bot.query("How many companies does Elon Musk run?")
print(response)
# Answer: 'Elon Musk runs four companies: Tesla, SpaceX, Neuralink, and The Boring Company.'
```
+10 -1
View File
@@ -1 +1,10 @@
from .embedchain import App, OpenSourceApp, PersonApp, PersonOpenSourceApp
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.Llama2App import Llama2App # noqa: F401
from embedchain.apps.OpenSourceApp import OpenSourceApp # noqa: F401
from embedchain.apps.PersonApp import (PersonApp, # noqa: F401
PersonOpenSourceApp)
+49
View File
@@ -0,0 +1,49 @@
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 = []
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
+152
View File
@@ -0,0 +1,152 @@
import logging
from typing import List
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 ImportError(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)
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)
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)
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)
return chat(messages).content
@staticmethod
def _get_messages(prompt: str) -> List[BaseMessage]:
from langchain.schema import HumanMessage, SystemMessage
return [SystemMessage(content="You are a helpful assistant."), HumanMessage(content=prompt)]
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
+36
View File
@@ -0,0 +1,36 @@
import os
from langchain.llms import Replicate
from embedchain.config import AppConfig
from embedchain.embedchain import EmbedChain
class Llama2App(EmbedChain):
"""
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.
query(query): finds answer to the given query using vector database and LLM.
"""
def __init__(self, config: AppConfig = None):
"""
:param config: AppConfig instance to load as configuration. 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: AppConfig = None):
# TODO: Move the model and other inputs into config
llm = Replicate(
model="a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
input={"temperature": 0.75, "max_length": 500, "top_p": 1},
)
return llm(prompt)
+65
View File
@@ -0,0 +1,65 @@
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 ValueError(
"The GPT4All python package is not installed. Please install it with `pip install GPT4All`"
) 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."
)
response = self.instance.generate(
prompt=prompt,
streaming=config.stream,
top_p=config.top_p,
max_tokens=config.max_tokens,
temp=config.temperature,
)
return response
+80
View File
@@ -0,0 +1,80 @@
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)
class EmbedChainPersonApp:
"""
Base class to create a person bot.
This bot behaves and speaks like a person.
:param person: name of the person, better if its a well known person.
:param config: BaseAppConfig instance to load as configuration.
"""
def __init__(self, person, config: BaseAppConfig = None):
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):
"""
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.
"""
template = Template(self.person_prompt + " " + default_prompt)
if config:
if config.template:
# Add person prompt to custom user template
config.template = Template(self.person_prompt + " " + config.template.template)
else:
# If no user template is present, use person prompt with the default template
config.template = template
else:
# if no config is present at all, initialize the config with person prompt and default template
config = QueryConfig(
template=template,
)
return config
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 chat(self, input_query, config: ChatConfig = None, dry_run=False):
config = self.add_person_template_to_config(DEFAULT_PROMPT_WITH_HISTORY, config)
return super().chat(input_query, config, dry_run)
class PersonOpenSourceApp(EmbedChainPersonApp, OpenSourceApp):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and OpenSourceApp
"""
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 chat(self, input_query, config: ChatConfig = None, dry_run=False):
config = self.add_person_template_to_config(DEFAULT_PROMPT_WITH_HISTORY, config)
return super().chat(input_query, config, dry_run)
View File
+19 -1
View File
@@ -5,6 +5,7 @@ class BaseChunker:
def __init__(self, text_splitter):
"""Initialize the chunker."""
self.text_splitter = text_splitter
self.data_type = None
def create_chunks(self, loader, src):
"""
@@ -22,10 +23,13 @@ class BaseChunker:
metadatas = []
for data in datas:
content = data["content"]
meta_data = data["meta_data"]
# add data type to meta data to allow query using data type
meta_data["data_type"] = self.data_type
url = meta_data["url"]
chunks = self.text_splitter.split_text(content)
chunks = self.get_chunks(content)
for chunk in chunks:
chunk_id = hashlib.sha256((chunk + url).encode()).hexdigest()
@@ -39,3 +43,17 @@ class BaseChunker:
"ids": ids,
"metadatas": metadatas,
}
def get_chunks(self, content):
"""
Returns chunks using text splitter instance.
Override in child class if custom logic.
"""
return self.text_splitter.split_text(content)
def set_data_type(self, data_type):
"""
set the data type of chunker
"""
self.data_type = data_type
+20
View File
@@ -0,0 +1,20 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
class DocsSiteChunker(BaseChunker):
"""Chunker for code docs site."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=500, chunk_overlap=50, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+6 -8
View File
@@ -5,18 +5,16 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 1000,
"chunk_overlap": 0,
"length_function": len,
}
class DocxFileChunker(BaseChunker):
"""Chunker for .docx file."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
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)
+20
View File
@@ -0,0 +1,20 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
class NotionChunker(BaseChunker):
"""Chunker for notion."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+6 -8
View File
@@ -5,18 +5,16 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 1000,
"chunk_overlap": 0,
"length_function": len,
}
class PdfFileChunker(BaseChunker):
"""Chunker for PDF file."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
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)
+6 -8
View File
@@ -5,18 +5,16 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 300,
"chunk_overlap": 0,
"length_function": len,
}
class QnaPairChunker(BaseChunker):
"""Chunker for QnA pair."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
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)
+6 -8
View File
@@ -5,18 +5,16 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 300,
"chunk_overlap": 0,
"length_function": len,
}
class TextChunker(BaseChunker):
"""Chunker for text."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
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)
+6 -8
View File
@@ -5,18 +5,16 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 500,
"chunk_overlap": 0,
"length_function": len,
}
class WebPageChunker(BaseChunker):
"""Chunker for web page."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
config = ChunkerConfig(chunk_size=500, chunk_overlap=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)
+6 -8
View File
@@ -5,18 +5,16 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
TEXT_SPLITTER_CHUNK_PARAMS = {
"chunk_size": 2000,
"chunk_overlap": 0,
"length_function": len,
}
class YoutubeVideoChunker(BaseChunker):
"""Chunker for Youtube video."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = TEXT_SPLITTER_CHUNK_PARAMS
text_splitter = RecursiveCharacterTextSplitter(**config)
config = ChunkerConfig(chunk_size=2000, 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)
+6 -6
View File
@@ -10,13 +10,13 @@ class ChunkerConfig(BaseConfig):
def __init__(
self,
chunk_size: Optional[int] = 4000,
chunk_overlap: Optional[int] = 200,
length_function: Optional[Callable[[str], int]] = len,
chunk_size: Optional[int] = None,
chunk_overlap: Optional[int] = None,
length_function: Optional[Callable[[str], int]] = None,
):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.length_function = length_function
self.chunk_size = chunk_size if chunk_size else 2000
self.chunk_overlap = chunk_overlap if chunk_overlap else 0
self.length_function = length_function if length_function else len
class LoaderConfig(BaseConfig):
+2
View File
@@ -33,6 +33,7 @@ class ChatConfig(QueryConfig):
max_tokens=None,
top_p=None,
stream: bool = False,
deployment_name=None,
):
"""
Initializes the ChatConfig instance.
@@ -68,6 +69,7 @@ class ChatConfig(QueryConfig):
top_p=top_p,
history=[0],
stream=stream,
deployment_name=deployment_name,
)
def set_history(self, history):
-81
View File
@@ -1,81 +0,0 @@
import logging
import os
from chromadb.utils import embedding_functions
from embedchain.config.BaseConfig import BaseConfig
class InitConfig(BaseConfig):
"""
Config to initialize an embedchain `App` instance.
"""
def __init__(self, log_level=None, ef=None, db=None, host=None, port=None):
"""
:param log_level: Optional. (String) Debug level
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'].
:param ef: Optional. Embedding function to use.
:param db: Optional. (Vector) database to use for embeddings.
:param host: Optional. Hostname for the database server.
:param port: Optional. Port for the database server.
"""
self._setup_logging(log_level)
self.ef = ef
self.db = db
self.host = host
self.port = port
return
def _set_embedding_function(self, ef):
self.ef = ef
return
def _set_embedding_function_to_default(self):
"""
Sets embedding function to default (`text-embedding-ada-002`).
:raises ValueError: If the template is not valid as template should contain
$context and $query
"""
if (
os.getenv("OPENAI_API_KEY") is None
and os.getenv("OPENAI_ORGANIZATION") is None
):
raise ValueError(
"OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided" # noqa:E501
)
self.ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name="text-embedding-ada-002",
)
return
def _set_db(self, db):
if db:
self.db = db
return
def _set_db_to_default(self):
"""
Sets database to default (`ChromaDb`).
"""
from embedchain.vectordb.chroma_db import ChromaDB
self.db = ChromaDB(ef=self.ef, host=self.host, port=self.port)
def _setup_logging(self, debug_level):
level = logging.WARNING # Default level
if debug_level is not None:
level = getattr(logging, debug_level.upper(), None)
if not isinstance(level, int):
raise ValueError(f"Invalid log level: {debug_level}")
logging.basicConfig(
format="%(asctime)s [%(name)s] [%(levelname)s] %(message)s", level=level
)
self.logger = logging.getLogger(__name__)
return
+18 -8
View File
@@ -17,7 +17,7 @@ DEFAULT_PROMPT = """
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.
I will provide you with our conversation history.
$context
@@ -28,8 +28,20 @@ DEFAULT_PROMPT_WITH_HISTORY = """
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\}*")
@@ -50,6 +62,7 @@ class QueryConfig(BaseConfig):
top_p=None,
history=None,
stream: bool = False,
deployment_name=None,
):
"""
Initializes the QueryConfig instance.
@@ -92,8 +105,9 @@ class QueryConfig(BaseConfig):
self.temperature = temperature if temperature else 0
self.max_tokens = max_tokens if max_tokens else 1000
self.model = model if model else "gpt-3.5-turbo-0613"
self.model = model
self.top_p = top_p if top_p else 1
self.deployment_name = deployment_name
if self.validate_template(template):
self.template = template
@@ -101,9 +115,7 @@ class QueryConfig(BaseConfig):
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"
)
raise ValueError("`template` should have `query`, `context` and `history` keys")
if not isinstance(stream, bool):
raise ValueError("`stream` should be bool")
@@ -117,9 +129,7 @@ class QueryConfig(BaseConfig):
: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
)
return re.search(query_re, template.template) and re.search(context_re, template.template)
else:
return (
re.search(query_re, template.template)
+8 -5
View File
@@ -1,5 +1,8 @@
from .AddConfig import AddConfig
from .BaseConfig import BaseConfig
from .ChatConfig import ChatConfig
from .InitConfig import InitConfig
from .QueryConfig import QueryConfig
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
+52
View File
@@ -0,0 +1,52 @@
import os
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):
"""
: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.
"""
super().__init__(
log_level=log_level,
embedding_fn=AppConfig.default_embedding_function(),
host=host,
port=port,
id=id,
collection_name=collection_name,
)
@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",
)
+95
View File
@@ -0,0 +1,95 @@
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,
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 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
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
+135
View File
@@ -0,0 +1,135 @@
from typing import Any
from chromadb.api.types import Documents, Embeddings
from dotenv import load_dotenv
from embedchain.config.vectordbs import ElasticsearchDBConfig
from embedchain.models import EmbeddingFunctions, Providers, VectorDatabases, VectorDimensions
from .BaseAppConfig import BaseAppConfig
load_dotenv()
class CustomAppConfig(BaseAppConfig):
"""
Config to initialize an embedchain custom `App` instance, with extra config options.
"""
def __init__(
self,
log_level=None,
embedding_fn: EmbeddingFunctions = None,
embedding_fn_model=None,
db=None,
host=None,
port=None,
id=None,
collection_name=None,
provider: Providers = None,
open_source_app_config=None,
deployment_name=None,
db_type: VectorDatabases = None,
es_config: ElasticsearchDBConfig = None,
):
"""
:param log_level: Optional. (String) Debug level
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'].
:param embedding_fn: Optional. Embedding function to use.
:param embedding_fn_model: Optional. Model name to use for embedding function.
:param db: Optional. (Vector) database 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 provider: Optional. (Providers): LLM Provider to use.
:param open_source_app_config: Optional. Config instance needed for open source apps.
:param db_type: Optional. type of Vector database to use.
:param es_config: Optional. elasticsearch database config to be used for connection
"""
if provider:
self.provider = provider
else:
raise ValueError("CustomApp must have a provider assigned.")
self.open_source_app_config = open_source_app_config
super().__init__(
log_level=log_level,
embedding_fn=CustomAppConfig.embedding_function(
embedding_function=embedding_fn, model=embedding_fn_model, deployment_name=deployment_name
),
db=db,
host=host,
port=port,
id=id,
collection_name=collection_name,
db_type=db_type,
vector_dim=CustomAppConfig.get_vector_dimension(embedding_function=embedding_fn),
es_config=es_config,
)
@staticmethod
def langchain_default_concept(embeddings: Any):
"""
Langchains default function layout for embeddings.
"""
def embed_function(texts: Documents) -> Embeddings:
return embeddings.embed_documents(texts)
return embed_function
@staticmethod
def embedding_function(embedding_function: EmbeddingFunctions, model: str = None, deployment_name: str = None):
if not isinstance(embedding_function, EmbeddingFunctions):
raise ValueError(
f"Invalid option: '{embedding_function}'. Expecting one of the following options: {list(map(lambda x: x.value, EmbeddingFunctions))}" # noqa: E501
)
if embedding_function == EmbeddingFunctions.OPENAI:
from langchain.embeddings import OpenAIEmbeddings
if model:
embeddings = OpenAIEmbeddings(model=model)
else:
if deployment_name:
embeddings = OpenAIEmbeddings(deployment=deployment_name)
else:
embeddings = OpenAIEmbeddings()
return CustomAppConfig.langchain_default_concept(embeddings)
elif embedding_function == EmbeddingFunctions.HUGGING_FACE:
from langchain.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name=model)
return CustomAppConfig.langchain_default_concept(embeddings)
elif embedding_function == EmbeddingFunctions.VERTEX_AI:
from langchain.embeddings import VertexAIEmbeddings
embeddings = VertexAIEmbeddings(model_name=model)
return CustomAppConfig.langchain_default_concept(embeddings)
elif embedding_function == EmbeddingFunctions.GPT4ALL:
# Note: We could use langchains GPT4ALL embedding, but it's not available in all versions.
from chromadb.utils import embedding_functions
return embedding_functions.SentenceTransformerEmbeddingFunction(model_name=model)
@staticmethod
def get_vector_dimension(embedding_function: EmbeddingFunctions):
if not isinstance(embedding_function, EmbeddingFunctions):
raise ValueError(f"Invalid option: '{embedding_function}'.")
if embedding_function == EmbeddingFunctions.OPENAI:
return VectorDimensions.OPENAI.value
elif embedding_function == EmbeddingFunctions.HUGGING_FACE:
return VectorDimensions.HUGGING_FACE.value
elif embedding_function == EmbeddingFunctions.VERTEX_AI:
return VectorDimensions.VERTEX_AI.value
elif embedding_function == EmbeddingFunctions.GPT4ALL:
return VectorDimensions.GPT4ALL.value
@@ -0,0 +1,40 @@
from chromadb.utils import embedding_functions
from .BaseAppConfig import BaseAppConfig
class OpenSourceAppConfig(BaseAppConfig):
"""
Config to initialize an embedchain custom `OpenSourceApp` instance, with extra config options.
"""
def __init__(self, log_level=None, host=None, port=None, id=None, collection_name=None, model=None):
"""
:param log_level: Optional. (String) Debug level
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'].
:param id: Optional. ID of the app. Document metadata will have this id.
:param collection_name: Optional. Collection name for the database.
:param host: Optional. Hostname for the database server.
:param port: Optional. Port for the database server.
:param model: Optional. GPT4ALL uses the model to instantiate the class.
So unlike `App`, it has to be provided before querying.
"""
self.model = model or "orca-mini-3b.ggmlv3.q4_0.bin"
super().__init__(
log_level=log_level,
embedding_fn=OpenSourceAppConfig.default_embedding_function(),
host=host,
port=port,
id=id,
collection_name=collection_name,
)
@staticmethod
def default_embedding_function():
"""
Sets embedding function to default (`all-MiniLM-L6-v2`).
:returns: The default embedding function
"""
return embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
View File
@@ -0,0 +1,15 @@
from typing import Dict, List, Union
from embedchain.config.BaseConfig import BaseConfig
class ElasticsearchDBConfig(BaseConfig):
"""
Config to initialize an elasticsearch client.
:param es_url. elasticsearch url or list of nodes url to be used for connection
:param ES_EXTRA_PARAMS: extra params dict that can be passed to elasticsearch.
"""
def __init__(self, es_url: Union[str, List[str]] = None, **ES_EXTRA_PARAMS: Dict[str, any]):
self.ES_URL = es_url
self.ES_EXTRA_PARAMS = ES_EXTRA_PARAMS
+1 -1
View File
@@ -1 +1 @@
from .data_formatter import DataFormatter
from .data_formatter import DataFormatter # noqa: F401
+27 -10
View File
@@ -1,10 +1,13 @@
from embedchain.chunkers.docs_site import DocsSiteChunker
from embedchain.chunkers.docx_file import DocxFileChunker
from embedchain.chunkers.notion import NotionChunker
from embedchain.chunkers.pdf_file import PdfFileChunker
from embedchain.chunkers.qna_pair import QnaPairChunker
from embedchain.chunkers.text import TextChunker
from embedchain.chunkers.web_page import WebPageChunker
from embedchain.chunkers.youtube_video import YoutubeVideoChunker
from embedchain.config import AddConfig
from embedchain.loaders.docs_site_loader import DocsSiteLoader
from embedchain.loaders.docx_file import DocxFileLoader
from embedchain.loaders.local_qna_pair import LocalQnaPairLoader
from embedchain.loaders.local_text import LocalTextLoader
@@ -41,9 +44,18 @@ class DataFormatter:
"text": LocalTextLoader(),
"docx": DocxFileLoader(),
"sitemap": SitemapLoader(),
"docs_site": DocsSiteLoader(),
}
lazy_loaders = ("notion",)
if data_type in loaders:
return loaders[data_type]
elif data_type in lazy_loaders:
if data_type == "notion":
from embedchain.loaders.notion import NotionLoader
return NotionLoader()
else:
raise ValueError(f"Unsupported data type: {data_type}")
else:
raise ValueError(f"Unsupported data type: {data_type}")
@@ -55,16 +67,21 @@ class DataFormatter:
:return: The chunker for the given data type.
:raises ValueError: If an unsupported data type is provided.
"""
chunkers = {
"youtube_video": YoutubeVideoChunker(config),
"pdf_file": PdfFileChunker(config),
"web_page": WebPageChunker(config),
"qna_pair": QnaPairChunker(config),
"text": TextChunker(config),
"docx": DocxFileChunker(config),
"sitemap": WebPageChunker(config),
chunker_classes = {
"youtube_video": YoutubeVideoChunker,
"pdf_file": PdfFileChunker,
"web_page": WebPageChunker,
"qna_pair": QnaPairChunker,
"text": TextChunker,
"docx": DocxFileChunker,
"sitemap": WebPageChunker,
"docs_site": DocsSiteChunker,
"notion": NotionChunker,
}
if data_type in chunkers:
return chunkers[data_type]
if data_type in chunker_classes:
chunker_class = chunker_classes[data_type]
chunker = chunker_class(config)
chunker.set_data_type(data_type)
return chunker
else:
raise ValueError(f"Unsupported data type: {data_type}")
+97 -224
View File
@@ -1,18 +1,16 @@
import logging
import os
from string import Template
import openai
from chromadb.utils import embedding_functions
from dotenv import load_dotenv
from langchain.docstore.document import Document
from langchain.memory import ConversationBufferMemory
from embedchain.config import AddConfig, ChatConfig, InitConfig, QueryConfig
from embedchain.config.QueryConfig import DEFAULT_PROMPT
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config import AddConfig, ChatConfig, QueryConfig
from embedchain.config.apps.BaseAppConfig import BaseAppConfig
from embedchain.config.QueryConfig import DOCS_SITE_PROMPT_TEMPLATE
from embedchain.data_formatter import DataFormatter
gpt4all_model = None
from embedchain.loaders.base_loader import BaseLoader
load_dotenv()
@@ -23,18 +21,20 @@ memory = ConversationBufferMemory()
class EmbedChain:
def __init__(self, config: InitConfig):
def __init__(self, config: BaseAppConfig):
"""
Initializes the EmbedChain instance, sets up a vector DB client and
creates a collection.
:param config: InitConfig instance to load as configuration.
:param config: BaseAppConfig instance to load as configuration.
"""
self.config = config
self.db_client = self.config.db.client
self.collection = self.config.db.collection
self.collection = self.config.db._get_or_create_collection(self.config.collection_name)
self.db = self.config.db
self.user_asks = []
self.is_docs_site_instance = False
self.online = False
def add(self, data_type, url, metadata=None, config: AddConfig = None):
"""
@@ -53,9 +53,9 @@ class EmbedChain:
data_formatter = DataFormatter(data_type, config)
self.user_asks.append([data_type, url, metadata])
self.load_and_embed(
data_formatter.loader, data_formatter.chunker, url, metadata
)
self.load_and_embed(data_formatter.loader, data_formatter.chunker, url, metadata)
if data_type in ("docs_site",):
self.is_docs_site_instance = True
def add_local(self, data_type, content, metadata=None, config: AddConfig = None):
"""
@@ -81,7 +81,7 @@ class EmbedChain:
metadata,
)
def load_and_embed(self, loader, chunker, src, metadata=None):
def load_and_embed(self, loader: BaseLoader, chunker: BaseChunker, src, metadata=None):
"""
Loads the data from the given URL, chunks it, and adds it to database.
@@ -96,19 +96,16 @@ class EmbedChain:
metadatas = embeddings_data["metadatas"]
ids = embeddings_data["ids"]
# get existing ids, and discard doc if any common id exist.
existing_docs = self.collection.get(
where = {"app_id": self.config.id} if self.config.id is not None else {}
# where={"url": src}
existing_ids = self.db.get(
ids=ids,
# where={"url": src}
where=where, # optional filter
)
existing_ids = set(existing_docs["ids"])
if len(existing_ids):
data_dict = {
id: (doc, meta) for id, doc, meta in zip(ids, documents, metadatas)
}
data_dict = {
id: value for id, value in data_dict.items() if id not in existing_ids
}
data_dict = {id: (doc, meta) for id, doc, meta in zip(ids, documents, metadatas)}
data_dict = {id: value for id, value in data_dict.items() if id not in existing_ids}
if not data_dict:
print(f"All data from {src} already exists in the database.")
@@ -117,20 +114,20 @@ class EmbedChain:
ids = list(data_dict.keys())
documents, metadatas = zip(*data_dict.values())
# Add app id in metadatas so that they can be queried on later
if self.config.id is not None:
metadatas = [{**m, "app_id": self.config.id} for m in metadatas]
# FIXME: Fix the error handling logic when metadatas or metadata is None
metadatas = metadatas if metadatas else []
metadata = metadata if metadata else {}
chunks_before_addition = self.count()
# Add metadata to each document
metadatas_with_metadata = [meta or metadata for meta in metadatas]
metadatas_with_metadata = [{**meta, **metadata} for meta in metadatas]
self.collection.add(
documents=documents, metadatas=list(metadatas_with_metadata), ids=ids
)
print(
(
f"Successfully saved {src}. New chunks count: "
f"{self.count() - chunks_before_addition}"
)
)
self.db.add(documents=documents, metadatas=list(metadatas_with_metadata), ids=ids)
print((f"Successfully saved {src}. New chunks count: " f"{self.count() - chunks_before_addition}"))
def _format_result(self, results):
return [
@@ -142,7 +139,10 @@ class EmbedChain:
)
]
def get_llm_model_answer(self, prompt):
def get_llm_model_answer(self):
"""
Usually implemented by child class
"""
raise NotImplementedError
def retrieve_from_database(self, input_query, config: QueryConfig):
@@ -154,17 +154,19 @@ class EmbedChain:
:param config: The query configuration.
:return: The content of the document that matched your query.
"""
result = self.collection.query(
query_texts=[
input_query,
],
where = {"app_id": self.config.id} if self.config.id is not None else {} # optional filter
contents = self.db.query(
input_query=input_query,
n_results=config.number_documents,
where=where,
)
results_formatted = self._format_result(result)
contents = [result[0].page_content for result in results_formatted]
return contents
def generate_prompt(self, input_query, contexts, config: QueryConfig):
def _append_search_and_context(self, context, web_search_result):
return f"{context}\nWeb Search Result: {web_search_result}"
def generate_prompt(self, input_query, contexts, config: QueryConfig, **kwargs):
"""
Generates a prompt based on the given query and context, ready to be
passed to an LLM
@@ -176,14 +178,13 @@ class EmbedChain:
:return: The prompt
"""
context_string = (" | ").join(contexts)
web_search_result = kwargs.get("web_search_result", "")
if web_search_result:
context_string = self._append_search_and_context(context_string, web_search_result)
if not config.history:
prompt = config.template.substitute(
context=context_string, query=input_query
)
prompt = config.template.substitute(context=context_string, query=input_query)
else:
prompt = config.template.substitute(
context=context_string, query=input_query, history=config.history
)
prompt = config.template.substitute(context=context_string, query=input_query, history=config.history)
return prompt
def get_answer_from_llm(self, prompt, config: ChatConfig):
@@ -198,7 +199,14 @@ class EmbedChain:
return self.get_llm_model_answer(prompt, config)
def query(self, input_query, config: QueryConfig = None):
def access_search_and_get_results(self, input_query):
from langchain.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
logging.info(f"Access search to get answers for {input_query}")
return search.run(input_query)
def query(self, input_query, config: QueryConfig = None, dry_run=False):
"""
Queries the vector database based on the given input query.
Gets relevant doc based on the query and then passes it to an
@@ -207,14 +215,29 @@ class EmbedChain:
:param input_query: The query to use.
:param config: Optional. The `QueryConfig` instance to use as
configuration options.
:param dry_run: Optional. A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response.
You can use it to test your prompt, including the context provided
by the vector database's doc retrieval.
The only thing the dry run does not consider is the cut-off due to
the `max_tokens` parameter.
:return: The answer to the query.
"""
if config is None:
config = QueryConfig()
if self.is_docs_site_instance:
config.template = DOCS_SITE_PROMPT_TEMPLATE
config.number_documents = 5
k = {}
if self.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
contexts = self.retrieve_from_database(input_query, config)
prompt = self.generate_prompt(input_query, contexts, config)
prompt = self.generate_prompt(input_query, contexts, config, **k)
logging.info(f"Prompt: {prompt}")
if dry_run:
return prompt
answer = self.get_answer_from_llm(prompt, config)
if isinstance(answer, str):
@@ -230,7 +253,7 @@ class EmbedChain:
yield chunk
logging.info(f"Answer: {streamed_answer}")
def chat(self, input_query, config: ChatConfig = None):
def chat(self, input_query, config: ChatConfig = None, dry_run=False):
"""
Queries the vector database on the given input query.
Gets relevant doc based on the query and then passes it to an
@@ -240,11 +263,22 @@ class EmbedChain:
:param input_query: The query to use.
:param config: Optional. The `ChatConfig` instance to use as
configuration options.
:param dry_run: Optional. A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response.
You can use it to test your prompt, including the context provided
by the vector database's doc retrieval.
The only thing the dry run does not consider is the cut-off due to
the `max_tokens` parameter.
:return: The answer to the query.
"""
if config is None:
config = ChatConfig()
if self.is_docs_site_instance:
config.template = DOCS_SITE_PROMPT_TEMPLATE
config.number_documents = 5
k = {}
if self.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
contexts = self.retrieve_from_database(input_query, config)
global memory
@@ -253,8 +287,12 @@ class EmbedChain:
if chat_history:
config.set_history(chat_history)
prompt = self.generate_prompt(input_query, contexts, config)
prompt = self.generate_prompt(input_query, contexts, config, **k)
logging.info(f"Prompt: {prompt}")
if dry_run:
return prompt
answer = self.get_answer_from_llm(prompt, config)
memory.chat_memory.add_user_message(input_query)
@@ -275,26 +313,13 @@ class EmbedChain:
memory.chat_memory.add_ai_message(streamed_answer)
logging.info(f"Answer: {streamed_answer}")
def dry_run(self, input_query, config: QueryConfig = None):
def set_collection(self, collection_name):
"""
A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response.
You can use it to test your prompt, including the context provided
by the vector database's doc retrieval.
The only thing the dry run does not consider is the cut-off due to
the `max_tokens` parameter.
Set the collection to use.
:param input_query: The query to use.
:param config: Optional. The `QueryConfig` instance to use as
configuration options.
:return: The prompt that would be sent to the LLM
:param collection_name: The name of the collection to use.
"""
if config is None:
config = QueryConfig()
contexts = self.retrieve_from_database(input_query, config)
prompt = self.generate_prompt(input_query, contexts, config)
logging.info(f"Prompt: {prompt}")
return prompt
self.collection = self.config.db._get_or_create_collection(collection_name)
def count(self):
"""
@@ -302,163 +327,11 @@ class EmbedChain:
:return: The number of embeddings.
"""
return self.collection.count()
return self.db.count()
def reset(self):
"""
Resets the database. Deletes all embeddings irreversibly.
`App` has to be reinitialized after using this method.
"""
self.db_client.reset()
class App(EmbedChain):
"""
The EmbedChain app.
Has two functions: add and query.
adds(data_type, url): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
dry_run(query): test your prompt without consuming tokens.
"""
def __init__(self, config: InitConfig = None):
"""
:param config: InitConfig instance to load as configuration. Optional.
"""
if config is None:
config = InitConfig()
if not config.ef:
config._set_embedding_function_to_default()
if not config.db:
config._set_db_to_default()
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig):
messages = []
messages.append({"role": "user", "content": prompt})
response = openai.ChatCompletion.create(
model=config.model,
messages=messages,
temperature=config.temperature,
max_tokens=config.max_tokens,
top_p=config.top_p,
stream=config.stream,
)
if config.stream:
return self._stream_llm_model_response(response)
else:
return response["choices"][0]["message"]["content"]
def _stream_llm_model_response(self, response):
"""
This is a generator for streaming response from the OpenAI completions API
"""
for line in response:
chunk = line["choices"][0].get("delta", {}).get("content", "")
yield chunk
class OpenSourceApp(EmbedChain):
"""
The OpenSource app.
Same as App, but uses an open source embedding model and LLM.
Has two function: add and query.
adds(data_type, url): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
"""
def __init__(self, config: InitConfig = None):
"""
:param config: InitConfig instance to load as configuration. Optional.
`ef` defaults to open source.
"""
print(
"Loading open source embedding model. This may take some time..."
) # noqa:E501
if not config:
config = InitConfig()
if not config.ef:
config._set_embedding_function(
embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
)
if not config.db:
config._set_db_to_default()
print("Successfully loaded open source embedding model.")
super().__init__(config)
def get_llm_model_answer(self, prompt, config: ChatConfig):
from gpt4all import GPT4All
global gpt4all_model
if gpt4all_model is None:
gpt4all_model = GPT4All("orca-mini-3b.ggmlv3.q4_0.bin")
response = gpt4all_model.generate(prompt=prompt, streaming=config.stream)
return response
class EmbedChainPersonApp:
"""
Base class to create a person bot.
This bot behaves and speaks like a person.
:param person: name of the person, better if its a well known person.
:param config: InitConfig instance to load as configuration.
"""
def __init__(self, person, config: InitConfig = None):
self.person = person
self.person_prompt = f"You are {person}. Whatever you say, you will always say in {person} style." # noqa:E501
self.template = Template(self.person_prompt + " " + DEFAULT_PROMPT)
if config is None:
config = InitConfig()
super().__init__(config)
class PersonApp(EmbedChainPersonApp, App):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and App
"""
def query(self, input_query, config: QueryConfig = None):
query_config = QueryConfig(
template=self.template,
)
return super().query(input_query, query_config)
def chat(self, input_query, config: ChatConfig = None):
chat_config = ChatConfig(
template=self.template,
)
return super().chat(input_query, chat_config)
class PersonOpenSourceApp(EmbedChainPersonApp, OpenSourceApp):
"""
The Person app.
Extends functionality from EmbedChainPersonApp and OpenSourceApp
"""
def query(self, input_query, config: QueryConfig = None):
query_config = QueryConfig(
template=self.template,
)
return super().query(input_query, query_config)
def chat(self, input_query, config: ChatConfig = None):
chat_config = ChatConfig(
template=self.template,
)
return super().chat(input_query, chat_config)
self.db.reset()
+9
View File
@@ -0,0 +1,9 @@
class BaseLoader:
def __init__(self):
pass
def load_data():
"""
Implemented by child classes
"""
pass
+100
View File
@@ -0,0 +1,100 @@
import logging
from urllib.parse import urljoin, urlparse
import requests
from bs4 import BeautifulSoup
from embedchain.loaders.base_loader import BaseLoader
class DocsSiteLoader(BaseLoader):
def __init__(self):
self.visited_links = set()
def _get_child_links_recursive(self, url):
parsed_url = urlparse(url)
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
current_path = parsed_url.path
response = requests.get(url)
if response.status_code != 200:
logging.info(f"Failed to fetch the website: {response.status_code}")
return
soup = BeautifulSoup(response.text, "html.parser")
all_links = [link.get("href") for link in soup.find_all("a")]
child_links = [link for link in all_links if link and link.startswith(current_path) and link != current_path]
absolute_paths = [urljoin(base_url, link) for link in child_links]
for link in absolute_paths:
if link not in self.visited_links:
self.visited_links.add(link)
self._get_child_links_recursive(link)
def _get_all_urls(self, url):
self.visited_links = set()
self._get_child_links_recursive(url)
urls = [link for link in self.visited_links if urlparse(link).netloc == urlparse(url).netloc]
return urls
def _load_data_from_url(self, url):
response = requests.get(url)
if response.status_code != 200:
logging.info(f"Failed to fetch the website: {response.status_code}")
return []
soup = BeautifulSoup(response.content, "html.parser")
selectors = [
"article.bd-article",
'article[role="main"]',
"div.md-content",
'div[role="main"]',
"div.container",
"div.section",
"article",
"main",
]
output = []
for selector in selectors:
element = soup.select_one(selector)
if element:
content = element.prettify()
break
else:
content = soup.get_text()
soup = BeautifulSoup(content, "html.parser")
ignored_tags = [
"nav",
"aside",
"form",
"header",
"noscript",
"svg",
"canvas",
"footer",
"script",
"style",
]
for tag in soup(ignored_tags):
tag.decompose()
content = " ".join(soup.stripped_strings)
output.append(
{
"content": content,
"meta_data": {"url": url},
}
)
return output
def load_data(self, url):
all_urls = self._get_all_urls(url)
output = []
for u in all_urls:
output.extend(self._load_data_from_url(u))
return output
+3 -1
View File
@@ -1,7 +1,9 @@
from langchain.document_loaders import Docx2txtLoader
from embedchain.loaders.base_loader import BaseLoader
class DocxFileLoader:
class DocxFileLoader(BaseLoader):
def load_data(self, url):
"""Load data from a .docx file."""
loader = Docx2txtLoader(url)
+4 -1
View File
@@ -1,4 +1,7 @@
class LocalQnaPairLoader:
from embedchain.loaders.base_loader import BaseLoader
class LocalQnaPairLoader(BaseLoader):
def load_data(self, content):
"""Load data from a local QnA pair."""
question, answer = content
+4 -1
View File
@@ -1,4 +1,7 @@
class LocalTextLoader:
from embedchain.loaders.base_loader import BaseLoader
class LocalTextLoader(BaseLoader):
def load_data(self, content):
"""Load data from a local text file."""
meta_data = {
+41
View File
@@ -0,0 +1,41 @@
import logging
import os
try:
from llama_index import download_loader
except ImportError:
raise ImportError("Notion requires extra dependencies. Install with `pip install embedchain[notion]`") from None
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class NotionLoader(BaseLoader):
def load_data(self, source):
"""Load data from a PDF file."""
NotionPageReader = download_loader("NotionPageReader")
# Reformat Id to match notion expectation
id = source[-32:]
formatted_id = f"{id[:8]}-{id[8:12]}-{id[12:16]}-{id[16:20]}-{id[20:]}"
logging.debug(f"Extracted notion page id as: {formatted_id}")
# Get page through the notion api
integration_token = os.getenv("NOTION_INTEGRATION_TOKEN")
reader = NotionPageReader(integration_token=integration_token)
documents = reader.load_data(page_ids=[formatted_id])
# Extract text
raw_text = documents[0].text
# Clean text
text = clean_string(raw_text)
return [
{
"content": text,
"meta_data": {"url": f"notion-{formatted_id}"},
}
]
+2 -1
View File
@@ -1,9 +1,10 @@
from langchain.document_loaders import PyPDFLoader
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class PdfFileLoader:
class PdfFileLoader(BaseLoader):
def load_data(self, url):
"""Load data from a PDF file."""
loader = PyPDFLoader(url)
+21 -4
View File
@@ -1,10 +1,15 @@
import logging
import requests
from bs4 import BeautifulSoup
from bs4.builder import ParserRejectedMarkup
from embedchain.loaders.base_loader import BaseLoader
from embedchain.loaders.web_page import WebPageLoader
from embedchain.utils import is_readable
class SitemapLoader:
class SitemapLoader(BaseLoader):
def load_data(self, sitemap_url):
"""
This method takes a sitemap URL as input and retrieves
@@ -17,8 +22,20 @@ class SitemapLoader:
response.raise_for_status()
soup = BeautifulSoup(response.text, "xml")
links = [link.text for link in soup.find_all("loc")]
links = [link.text for link in soup.find_all("loc") if link.parent.name == "url"]
if len(links) == 0:
# Get all <loc> tags as a fallback. This might include images.
links = [link.text for link in soup.find_all("loc")]
for link in links:
each_load_data = web_page_loader.load_data(link)
output.append(each_load_data)
try:
each_load_data = web_page_loader.load_data(link)
if is_readable(each_load_data[0].get("content")):
output.append(each_load_data)
else:
logging.warning(f"Page is not readable (too many invalid characters): {link}")
except ParserRejectedMarkup as e:
logging.error(f"Failed to parse {link}: {e}")
return [data[0] for data in output]
+50 -20
View File
@@ -1,40 +1,70 @@
import logging
import requests
from bs4 import BeautifulSoup
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class WebPageLoader:
class WebPageLoader(BaseLoader):
def load_data(self, url):
"""Load data from a web page."""
response = requests.get(url)
data = response.content
soup = BeautifulSoup(data, "html.parser")
for tag in soup(
[
"nav",
"aside",
"form",
"header",
"noscript",
"svg",
"canvas",
"footer",
"script",
"style",
]
):
tag.string = " "
output = []
original_size = len(str(soup.get_text()))
tags_to_exclude = [
"nav",
"aside",
"form",
"header",
"noscript",
"svg",
"canvas",
"footer",
"script",
"style",
]
for tag in soup(tags_to_exclude):
tag.decompose()
ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"]
for id in ids_to_exclude:
tags = soup.find_all(id=id)
for tag in tags:
tag.decompose()
classes_to_exclude = [
"elementor-location-header",
"navbar-header",
"nav",
"header-sidebar-wrapper",
"blog-sidebar-wrapper",
"related-posts",
]
for class_name in classes_to_exclude:
tags = soup.find_all(class_=class_name)
for tag in tags:
tag.decompose()
content = soup.get_text()
content = clean_string(content)
cleaned_size = len(content)
if original_size != 0:
logging.info(
f"[{url}] Cleaned page size: {cleaned_size} characters, down from {original_size} (shrunk: {original_size-cleaned_size} chars, {round((1-(cleaned_size/original_size)) * 100, 2)}%)" # noqa:E501
)
meta_data = {
"url": url,
}
output.append(
return [
{
"content": content,
"meta_data": meta_data,
}
)
return output
]
+2 -1
View File
@@ -1,9 +1,10 @@
from langchain.document_loaders import YoutubeLoader
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
class YoutubeVideoLoader:
class YoutubeVideoLoader(BaseLoader):
def load_data(self, url):
"""Load data from a Youtube video."""
loader = YoutubeLoader.from_youtube_url(url, add_video_info=True)
+8
View File
@@ -0,0 +1,8 @@
from enum import Enum
class EmbeddingFunctions(Enum):
OPENAI = "OPENAI"
HUGGING_FACE = "HUGGING_FACE"
VERTEX_AI = "VERTEX_AI"
GPT4ALL = "GPT4ALL"
+9
View File
@@ -0,0 +1,9 @@
from enum import Enum
class Providers(Enum):
OPENAI = "OPENAI"
ANTHROPHIC = "ANTHPROPIC"
VERTEX_AI = "VERTEX_AI"
GPT4ALL = "GPT4ALL"
AZURE_OPENAI = "AZURE_OPENAI"
+6
View File
@@ -0,0 +1,6 @@
from enum import Enum
class VectorDatabases(Enum):
CHROMADB = "CHROMADB"
ELASTICSEARCH = "ELASTICSEARCH"
+9
View File
@@ -0,0 +1,9 @@
from enum import Enum
# vector length created by embedding fn
class VectorDimensions(Enum):
GPT4ALL = 384
OPENAI = 1536
VERTEX_AI = 768
HUGGING_FACE = 384
+4
View File
@@ -0,0 +1,4 @@
from .EmbeddingFunctions import EmbeddingFunctions # noqa: F401
from .Providers import Providers # noqa: F401
from .VectorDatabases import VectorDatabases # noqa: F401
from .VectorDimensions import VectorDimensions # noqa: F401
+56
View File
@@ -1,4 +1,6 @@
import logging
import re
import string
def clean_string(text):
@@ -33,3 +35,57 @@ def clean_string(text):
cleaned_text = re.sub(r"([^\w\s])\1*", r"\1", cleaned_text)
return cleaned_text
def is_readable(s):
"""
Heuristic to determine if a string is "readable" (mostly contains printable characters and forms meaningful words)
:param s: string
:return: True if the string is more than 95% printable.
"""
try:
printable_ratio = sum(c in string.printable for c in s) / len(s)
except ZeroDivisionError:
logging.warning("Empty string processed as unreadable")
printable_ratio = 0
return printable_ratio > 0.95 # 95% of characters are printable
def use_pysqlite3():
"""
Swap std-lib sqlite3 with pysqlite3.
"""
import platform
import sqlite3
if platform.system() == "Linux" and sqlite3.sqlite_version_info < (3, 35, 0):
try:
# According to the Chroma team, this patch only works on Linux
import datetime
import subprocess
import sys
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "pysqlite3-binary", "--quiet", "--disable-pip-version-check"]
)
__import__("pysqlite3")
sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
# Let the user know what happened.
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
print(
f"{current_time} [embedchain] [INFO]",
"Swapped std-lib sqlite3 with pysqlite3 for ChromaDb compatibility.",
f"Your original version was {sqlite3.sqlite_version}.",
)
except Exception as e:
# Escape all exceptions
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
print(
f"{current_time} [embedchain] [ERROR]",
"Failed to swap std-lib sqlite3 with pysqlite3 for ChromaDb compatibility.",
"Error:",
e,
)
+15 -1
View File
@@ -3,7 +3,6 @@ class BaseVectorDB:
def __init__(self):
self.client = self._get_or_create_db()
self.collection = self._get_or_create_collection()
def _get_or_create_db(self):
"""Get or create the database."""
@@ -11,3 +10,18 @@ class BaseVectorDB:
def _get_or_create_collection(self):
raise NotImplementedError
def get(self):
raise NotImplementedError
def add(self):
raise NotImplementedError
def query(self):
raise NotImplementedError
def count(self):
raise NotImplementedError
def reset(self):
raise NotImplementedError
+95 -19
View File
@@ -1,8 +1,18 @@
import logging
import os
from typing import Any, Dict, List
import chromadb
from chromadb.utils import embedding_functions
from chromadb.errors import InvalidDimensionException
from langchain.docstore.document import Document
try:
import chromadb
except RuntimeError:
from embedchain.utils import use_pysqlite3
use_pysqlite3()
import chromadb
from chromadb.config import Settings
from embedchain.vectordb.base_vector_db import BaseVectorDB
@@ -10,33 +20,99 @@ from embedchain.vectordb.base_vector_db import BaseVectorDB
class ChromaDB(BaseVectorDB):
"""Vector database using ChromaDB."""
def __init__(self, db_dir=None, ef=None, host=None, port=None):
self.ef = ef
def __init__(self, db_dir=None, embedding_fn=None, host=None, port=None):
self.embedding_fn = embedding_fn
if not hasattr(embedding_fn, "__call__"):
raise ValueError("Embedding function is not a function")
if host and port:
logging.info(f"Connecting to ChromaDB server: {host}:{port}")
self.client_settings = chromadb.config.Settings(
chroma_api_impl="rest",
chroma_server_host=host,
chroma_server_http_port=port,
)
self.settings = Settings(chroma_server_host=host, chroma_server_http_port=port)
self.client = chromadb.HttpClient(self.settings)
else:
if db_dir is None:
db_dir = "db"
self.client_settings = chromadb.config.Settings(
chroma_db_impl="duckdb+parquet",
persist_directory=db_dir,
anonymized_telemetry=False,
self.settings = Settings(anonymized_telemetry=False, allow_reset=True)
self.client = chromadb.PersistentClient(
path=db_dir,
settings=self.settings,
)
super().__init__()
def _get_or_create_db(self):
"""Get or create the database."""
return chromadb.Client(self.client_settings)
return self.client
def _get_or_create_collection(self):
def _get_or_create_collection(self, name):
"""Get or create the collection."""
return self.client.get_or_create_collection(
"embedchain_store",
embedding_function=self.ef,
self.collection = self.client.get_or_create_collection(
name=name,
embedding_function=self.embedding_fn,
)
return self.collection
def get(self, ids: List[str], where: Dict[str, any]) -> List[str]:
"""
Get existing doc ids present in vector database
:param ids: list of doc ids to check for existance
:param where: Optional. to filter data
"""
existing_docs = self.collection.get(
ids=ids,
where=where, # optional filter
)
return set(existing_docs["ids"])
def add(self, documents: List[str], metadatas: List[object], ids: List[str]) -> Any:
"""
add data in vector database
:param documents: list of texts to add
:param metadatas: list of metadata associated with docs
:param ids: ids of docs
"""
self.collection.add(documents=documents, metadatas=metadatas, ids=ids)
def _format_result(self, results):
return [
(Document(page_content=result[0], metadata=result[1] or {}), result[2])
for result in zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0],
)
]
def query(self, input_query: List[str], n_results: int, where: Dict[str, any]) -> List[str]:
"""
query contents from vector data base based on vector similarity
:param input_query: list of query string
:param n_results: no of similar documents to fetch from database
:param where: Optional. to filter data
:return: The content of the document that matched your query.
"""
try:
result = self.collection.query(
query_texts=[
input_query,
],
n_results=n_results,
where=where,
)
except InvalidDimensionException as e:
raise InvalidDimensionException(
e.message()
+ ". This is commonly a side-effect when an embedding function, different from the one used to add the embeddings, is used to retrieve an embedding from the database." # noqa E501
) from None
results_formatted = self._format_result(result)
contents = [result[0].page_content for result in results_formatted]
return contents
def count(self) -> int:
return self.collection.count()
def reset(self):
# Delete all data from the database
self.client.reset()
+136
View File
@@ -0,0 +1,136 @@
from typing import Any, Callable, Dict, List
try:
from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulk
except ImportError:
raise ImportError(
"Elasticsearch requires extra dependencies. Install with `pip install embedchain[elasticsearch]`"
) from None
from embedchain.config import ElasticsearchDBConfig
from embedchain.models.VectorDimensions import VectorDimensions
from embedchain.vectordb.base_vector_db import BaseVectorDB
class ElasticsearchDB(BaseVectorDB):
def __init__(
self,
es_config: ElasticsearchDBConfig = None,
embedding_fn: Callable[[list[str]], list[str]] = None,
vector_dim: VectorDimensions = None,
collection_name: str = None,
):
"""
Elasticsearch as vector database
:param es_config. elasticsearch database config to be used for connection
:param embedding_fn: Function to generate embedding vectors.
:param vector_dim: Vector dimension generated by embedding fn
:param collection_name: Optional. Collection name for the database.
"""
if not hasattr(embedding_fn, "__call__"):
raise ValueError("Embedding function is not a function")
if es_config is None:
raise ValueError("ElasticsearchDBConfig is required")
if vector_dim is None:
raise ValueError("Vector Dimension is required to refer correct index and mapping")
if collection_name is None:
raise ValueError("collection name is required. It cannot be empty")
self.embedding_fn = embedding_fn
self.client = Elasticsearch(es_config.ES_URL, **es_config.ES_EXTRA_PARAMS)
self.vector_dim = vector_dim
self.es_index = f"{collection_name}_{self.vector_dim}"
index_settings = {
"mappings": {
"properties": {
"text": {"type": "text"},
"text_vector": {"type": "dense_vector", "index": False, "dims": self.vector_dim},
}
}
}
if not self.client.indices.exists(index=self.es_index):
# create index if not exist
print("Creating index", self.es_index, index_settings)
self.client.indices.create(index=self.es_index, body=index_settings)
super().__init__()
def _get_or_create_db(self):
return self.client
def _get_or_create_collection(self, name):
"""Note: nothing to return here. Discuss later"""
def get(self, ids: List[str], where: Dict[str, any]) -> List[str]:
"""
Get existing doc ids present in vector database
:param ids: list of doc ids to check for existance
:param where: Optional. to filter data
"""
query = {"bool": {"must": [{"ids": {"values": ids}}]}}
if "app_id" in where:
app_id = where["app_id"]
query["bool"]["must"].append({"term": {"metadata.app_id": app_id}})
response = self.client.search(index=self.es_index, query=query, _source=False)
docs = response["hits"]["hits"]
ids = [doc["_id"] for doc in docs]
return set(ids)
def add(self, documents: List[str], metadatas: List[object], ids: List[str]) -> Any:
"""
add data in vector database
:param documents: list of texts to add
:param metadatas: list of metadata associated with docs
:param ids: ids of docs
"""
docs = []
embeddings = self.embedding_fn(documents)
for id, text, metadata, text_vector in zip(ids, documents, metadatas, embeddings):
docs.append(
{
"_index": self.es_index,
"_id": id,
"_source": {"text": text, "metadata": metadata, "text_vector": text_vector},
}
)
bulk(self.client, docs)
self.client.indices.refresh(index=self.es_index)
return
def query(self, input_query: List[str], n_results: int, where: Dict[str, any]) -> List[str]:
"""
query contents from vector data base based on vector similarity
:param input_query: list of query string
:param n_results: no of similar documents to fetch from database
:param where: Optional. to filter data
"""
input_query_vector = self.embedding_fn(input_query)
query_vector = input_query_vector[0]
query = {
"script_score": {
"query": {"bool": {"must": [{"exists": {"field": "text"}}]}},
"script": {
"source": "cosineSimilarity(params.input_query_vector, 'text_vector') + 1.0",
"params": {"input_query_vector": query_vector},
},
}
}
if "app_id" in where:
app_id = where["app_id"]
query["script_score"]["query"]["bool"]["must"] = [{"term": {"metadata.app_id": app_id}}]
_source = ["text"]
response = self.client.search(index=self.es_index, query=query, _source=_source, size=n_results)
docs = response["hits"]["hits"]
contents = [doc["_source"]["text"] for doc in docs]
return contents
def count(self) -> int:
query = {"match_all": {}}
response = self.client.count(index=self.es_index, query=query)
doc_count = response["count"]
return doc_count
def reset(self):
# Delete all data from the database
if self.client.indices.exists(index=self.es_index):
# delete index in Es
self.client.indices.delete(index=self.es_index)
-1
View File
@@ -1 +0,0 @@
__version__ = "0.0.22"
+1
View File
@@ -0,0 +1 @@
.git
+18
View File
@@ -0,0 +1,18 @@
## 🐳 Docker Setup
- To setup full stack app using docker, run the following command inside this folder using your terminal.
```bash
docker-compose up --build
```
📝 Note: The build command might take a while to install all the packages depending on your system resources.
## 🚀 Usage Instructions
- Go to [http://localhost:3000/](http://localhost:3000/) in your browser to view the dashboard.
- Add your `OpenAI API key` 🔑 in the Settings.
- Create a new bot and you'll be navigated to its page.
- Here you can add your data sources and then chat with the bot.
🎉 Happy Chatting! 🎉
@@ -0,0 +1,7 @@
__pycache__/
database
pyenv
venv
.env
.git
trash_files/
+6
View File
@@ -0,0 +1,6 @@
__pycache__
database
pyenv
venv
.env
trash_files/
+11
View File
@@ -0,0 +1,11 @@
FROM python:3.11 AS backend
WORKDIR /usr/src/app/backend
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "server.py"]
+14
View File
@@ -0,0 +1,14 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class APIKey(db.Model):
id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.String(255), nullable=False)
class BotList(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
slug = db.Column(db.String(255), nullable=False, unique=True)
+5
View File
@@ -0,0 +1,5 @@
import os
ROOT_DIRECTORY = os.getcwd()
DB_DIRECTORY_OPEN_AI = os.path.join(os.getcwd(), "database", "open_ai")
DB_DIRECTORY_OPEN_SOURCE = os.path.join(os.getcwd(), "database", "open_source")
Binary file not shown.
@@ -0,0 +1,32 @@
import os
from flask import Blueprint, jsonify, make_response, request
from models import APIKey
from paths import DB_DIRECTORY_OPEN_AI
from embedchain import App
chat_response_bp = Blueprint("chat_response", __name__)
# Chat Response for user query
@chat_response_bp.route("/api/get_answer", methods=["POST"])
def get_answer():
try:
data = request.get_json()
query = data.get("query")
embedding_model = data.get("embedding_model")
app_type = data.get("app_type")
if embedding_model == "open_ai":
os.chdir(DB_DIRECTORY_OPEN_AI)
api_key = APIKey.query.first().key
os.environ["OPENAI_API_KEY"] = api_key
if app_type == "app":
chat_bot = App()
response = chat_bot.chat(query)
return make_response(jsonify({"response": response}), 200)
except Exception as e:
return make_response(jsonify({"error": str(e)}), 400)
@@ -0,0 +1,72 @@
from flask import Blueprint, jsonify, make_response, request
from models import APIKey, BotList, db
dashboard_bp = Blueprint("dashboard", __name__)
# Set Open AI Key
@dashboard_bp.route("/api/set_key", methods=["POST"])
def set_key():
data = request.get_json()
api_key = data["openAIKey"]
existing_key = APIKey.query.first()
if existing_key:
existing_key.key = api_key
else:
new_key = APIKey(key=api_key)
db.session.add(new_key)
db.session.commit()
return make_response(jsonify(message="API key saved successfully"), 200)
# Check OpenAI Key
@dashboard_bp.route("/api/check_key", methods=["GET"])
def check_key():
existing_key = APIKey.query.first()
if existing_key:
return make_response(jsonify(status="ok", message="OpenAI Key exists"), 200)
else:
return make_response(jsonify(status="fail", message="No OpenAI Key present"), 200)
# Create a bot
@dashboard_bp.route("/api/create_bot", methods=["POST"])
def create_bot():
data = request.get_json()
name = data["name"]
slug = name.lower().replace(" ", "_")
existing_bot = BotList.query.filter_by(slug=slug).first()
if existing_bot:
return (make_response(jsonify(message="Bot already exists"), 400),)
new_bot = BotList(name=name, slug=slug)
db.session.add(new_bot)
db.session.commit()
return make_response(jsonify(message="Bot created successfully"), 200)
# Delete a bot
@dashboard_bp.route("/api/delete_bot", methods=["POST"])
def delete_bot():
data = request.get_json()
slug = data.get("slug")
bot = BotList.query.filter_by(slug=slug).first()
if bot:
db.session.delete(bot)
db.session.commit()
return make_response(jsonify(message="Bot deleted successfully"), 200)
return make_response(jsonify(message="Bot not found"), 400)
# Get the list of bots
@dashboard_bp.route("/api/get_bots", methods=["GET"])
def get_bots():
bots = BotList.query.all()
bot_list = []
for bot in bots:
bot_list.append(
{
"name": bot.name,
"slug": bot.slug,
}
)
return jsonify(bot_list)
@@ -0,0 +1,27 @@
import os
from flask import Blueprint, jsonify, make_response, request
from models import APIKey
from paths import DB_DIRECTORY_OPEN_AI
from embedchain import App
sources_bp = Blueprint("sources", __name__)
# API route to add data sources
@sources_bp.route("/api/add_sources", methods=["POST"])
def add_sources():
try:
embedding_model = request.json.get("embedding_model")
name = request.json.get("name")
value = request.json.get("value")
if embedding_model == "open_ai":
os.chdir(DB_DIRECTORY_OPEN_AI)
api_key = APIKey.query.first().key
os.environ["OPENAI_API_KEY"] = api_key
chat_bot = App()
chat_bot.add(name, value)
return make_response(jsonify(message="Sources added successfully"), 200)
except Exception as e:
return make_response(jsonify(message=f"Error adding sources: {str(e)}"), 400)
+27
View File
@@ -0,0 +1,27 @@
import os
from flask import Flask
from models import db
from paths import DB_DIRECTORY_OPEN_AI, ROOT_DIRECTORY
from routes.chat_response import chat_response_bp
from routes.dashboard import dashboard_bp
from routes.sources import sources_bp
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(ROOT_DIRECTORY, "database", "user_data.db")
app.register_blueprint(dashboard_bp)
app.register_blueprint(sources_bp)
app.register_blueprint(chat_response_bp)
# Initialize the app on startup
def load_app():
os.makedirs(DB_DIRECTORY_OPEN_AI, exist_ok=True)
db.init_app(app)
with app.app_context():
db.create_all()
if __name__ == "__main__":
load_app()
app.run(host="0.0.0.0", debug=True, port=8000)
+22
View File
@@ -0,0 +1,22 @@
version: "3.9"
services:
backend:
container_name: embedchain_backend
restart: unless-stopped
build:
context: backend
dockerfile: Dockerfile
ports:
- "8000:8000"
frontend:
container_name: embedchain_frontend
restart: unless-stopped
build:
context: frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
depends_on:
- "backend"
@@ -0,0 +1,7 @@
node_modules/
build
dist
.env
.git
.next/
trash_files/
@@ -0,0 +1,3 @@
{
"extends": ["next/babel", "next/core-web-vitals"]
}

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