Compare commits

...

51 Commits

Author SHA1 Message Date
Deshraj Yadav 4eb91683a9 Bump version to 0.0.68 (#785) 2023-10-09 12:41:30 -07:00
Sidharth Mohanty 0cb78b9067 Add Hugging Face Hub LLM support (#762) 2023-10-09 12:15:22 -07:00
Sidharth Mohanty e226a89637 Add Jina LLM support (#760) 2023-10-09 12:06:36 -07:00
SerSamgy 431f8c2c6a Feat: Improve test coverage of DocsSiteLoader (#758) 2023-10-09 12:03:40 -07:00
Rupesh Bansal 19a9141c2d Added Clip dependency (#778) 2023-10-09 12:02:45 -07:00
Rupesh Bansal bc649b9a85 Added docs for skip_embedding and embeddings argument of vectordbs (#784) 2023-10-09 12:01:44 -07:00
Shreya Shrivastava 702067e521 Improve user readability of .env.example (#781) 2023-10-09 12:01:23 -07:00
Sidharth Mohanty 03a84daf9d Add Cohere LLM support (#751) 2023-10-09 11:54:24 -07:00
Sidharth Mohanty b91d922600 Improve tests (#780) 2023-10-09 11:26:21 -07:00
Prikshit ed02aebf9a Rename 'PersonApp.py' to 'person_app.py' (#752) 2023-10-09 08:05:11 -07:00
Prikshit 1a048390fd Rename _get_athrophic_answer to _get_answer (#775) 2023-10-07 02:22:49 -07:00
Richard Awoyemi 1741d3bef6 [fix]: Fix sitemap loader (#753) 2023-10-06 16:24:15 -07:00
Ojuswi Rastogi 540a0a3685 [feat]: Add support for XML file format (#757) 2023-10-06 15:39:32 -07:00
SerSamgy d2fd3ce434 [tests]: add more tests for pdf_file loader (#769) 2023-10-06 14:10:45 -07:00
Deshraj Yadav ea76868d65 [docs]: update readme (#767) 2023-10-04 18:14:16 -07:00
Deshraj Yadav f31351bedb [OpenSearch] Small bug fixes in query 2023-10-04 18:07:46 -07:00
Deshraj Yadav 8863983c7b [Images] Remove 'clip' from the list of depdencies since pypi doesn't allow it (#766) 2023-10-04 17:08:27 -07:00
Deshraj Yadav 64a34cac32 [OpenSearch] Add chunks specific to an app_id if present (#765) 2023-10-04 15:46:22 -07:00
Deshraj Yadav 352e71461d [OpenSearch]: Fix add() and query() for opensearch db (#764) 2023-10-04 12:53:07 -07:00
Deshraj Yadav 87d0b5c76f [bugfix] Fix issue when llm config is not defined (#763) 2023-10-04 12:08:21 -07:00
Rupesh Bansal d0af018b8d Add support for image dataset (#571)
Co-authored-by: Rupesh Bansal <rupeshbansal@Shankars-MacBook-Air.local>
2023-10-04 09:50:40 +05:30
Taranjeet Singh 55e9a1cbd6 fix: update notebook link and link in example (#748) 2023-10-01 04:43:26 +05:30
Taranjeet Singh 65bafb75b1 feat: bump version to 0.0.65 (#747) 2023-10-01 04:28:26 +05:30
Taranjeet Singh 01fd1c2437 docs: add codecov.io badge in readme (#746) 2023-10-01 04:24:49 +05:30
Taranjeet Singh 78ba4468b0 docs: add release note & fix emoji in contribution (#745) 2023-10-01 04:21:44 +05:30
Taranjeet Singh 8581c7ecce docs: add contribution section (#744) 2023-10-01 04:16:09 +05:30
Taranjeet Singh 9be6fe6bc3 docs: add community section with links and showcase (#743) 2023-10-01 04:06:23 +05:30
Taranjeet Singh 8c506da21e docs: add google form link (#742) 2023-10-01 03:54:35 +05:30
Taranjeet Singh c02002eb4b fix: typo in gpt-4 model (#741) 2023-10-01 00:49:27 +05:30
Rajat Gupta 57ecfca862 Setup codecov.io to measure test coverage #696 (#739) 2023-09-30 23:56:49 +05:30
Taranjeet Singh 28d41e9397 fix: improve add data section (#740) 2023-09-30 23:43:44 +05:30
Prikshit 7a1866d280 Update outdated 'OpenAI' Model (#735) 2023-09-30 23:42:33 +05:30
Rhythm Sharma d229b108c3 fix: Rename _get_athrophic_answer to _get_answer in anthropic llm class (#732) 2023-09-30 12:17:48 +05:30
Taranjeet Singh 39640cb697 feat: bump version to 0.0.64 (#733) 2023-09-30 12:16:22 +05:30
cachho 9ecf2e9feb feat: One App (#635)
Co-authored-by: Taranjeet Singh <reachtotj@gmail.com>
2023-09-30 12:00:43 +05:30
Taranjeet Singh 2db07cdb1f docs: add gpt-4 as llm FAQ (#731) 2023-09-30 10:24:49 +05:30
Taranjeet Singh faa29ef285 fix: Update doc heading name (#730) 2023-09-30 10:01:57 +05:30
Prikshit 16b0d5b829 Rename method _get_gpt4all_answer to _get_answer (#727) 2023-09-30 09:45:31 +05:30
Deshraj Yadav 6ae33d04b8 [OpenSearch] Add support for filtering docs based on app_id in opensearch db (#729) 2023-09-29 15:19:10 -07:00
Deshraj Yadav 333eb8d60f Update version to 0.0.62 (#726) 2023-09-28 15:03:20 -07:00
Deshraj Yadav 414c69fd62 Add support for OpenSearch as vector database (#725) 2023-09-29 03:24:42 +05:30
Prikshit 9951b58005 Rename _get_azure_openai_answer to _get_answer (#724) 2023-09-29 00:13:49 +05:30
Osama Nabih e73ed82ae9 chore: Renamed EmbeddingFunctions.py to follow snakecase convention (#669)
Signed-off-by: Osama Nabih <nabih_the_4th@yahoo.com>
2023-09-28 11:35:10 +05:30
Tushar kalsi 8bde34a24f Fix #718 Added Slack in Mint.Js (#720) 2023-09-28 11:32:33 +05:30
Taranjeet Singh b58380e505 docs: add langsmith integration. (#717) 2023-09-28 03:16:53 +05:30
Ayush Mishra 16f8de810c Chore/follow_snake_conventions_in_config (#716) 2023-09-27 21:38:42 +05:30
Taranjeet Singh 70f2de4fd3 feat: bump version to 0.0.61 (#709) 2023-09-27 10:53:26 +05:30
Subhajit Ghosh a5d5e5825f Renamed apps/App.py to follow snake case convention (#708) 2023-09-27 10:35:22 +05:30
Taranjeet Singh 0f16c72762 fix: use openai llm via langchain (#670)
Co-authored-by: Deshraj Yadav <deshrajdry@gmail.com>
2023-09-27 10:34:02 +05:30
Subhajit Ghosh 9ca7a0d6d1 Rename models/Providers.py to follow snake case convention (#707) 2023-09-27 10:31:08 +05:30
Subhajit Ghosh 7d5bfd8c9f Renamed models/VectorDimensions.py to follow snake case convention (#706) 2023-09-27 10:15:28 +05:30
128 changed files with 3086 additions and 645 deletions
+1 -1
View File
@@ -1 +1 @@
OPENAI_API_KEY=
OPENAI_API_KEY="your-openai-api-key"
+1 -1
View File
@@ -37,4 +37,4 @@ jobs:
- name: Publish distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@release/v1
+9
View File
@@ -26,3 +26,12 @@ jobs:
run: make ci_lint
- name: Test with pytest
run: make ci_test
- name: Generate coverage report
run: make coverage
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
with:
file: coverage.xml
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+20 -3
View File
@@ -4,11 +4,25 @@ PIP := $(PYTHON) -m pip
PROJECT_NAME := embedchain
# Targets
.PHONY: install format lint clean test ci_lint ci_test
.PHONY: install format lint clean test ci_lint ci_test coverage
install:
$(PIP) install --upgrade pip
$(PIP) install -e .[dev]
poetry install
install_all:
poetry install --all-extras
install_es:
poetry install --extras elasticsearch
install_opensearch:
poetry install --extras opensearch
shell:
poetry shell
py_shell:
poetry run python
format:
$(PYTHON) -m black .
@@ -28,3 +42,6 @@ ci_lint:
ci_test:
poetry run pytest
coverage:
poetry run pytest --cov=$(PROJECT_NAME) --cov-report=xml
+9 -6
View File
@@ -6,8 +6,9 @@
[![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)
[![codecov](https://codecov.io/gh/embedchain/embedchain/graph/badge.svg?token=EMRRHZXW1Q)](https://codecov.io/gh/embedchain/embedchain)
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/embedchain/tree/main/embedchain-js)
## Community
@@ -27,7 +28,7 @@ pip install --upgrade embedchain
Try out embedchain in your browser:
[![Open in Colab](https://camo.githubusercontent.com/84f0493939e0c4de4e6dbe113251b4bfb5353e57134ffd9fcab6b8714514d4d1/68747470733a2f2f636f6c61622e72657365617263682e676f6f676c652e636f6d2f6173736574732f636f6c61622d62616467652e737667)](https://colab.research.google.com/drive/138lMWhENGeEu7Q1-6lNbNTHGLZXBBz_B?usp=sharing)
[![Open in Colab](https://camo.githubusercontent.com/84f0493939e0c4de4e6dbe113251b4bfb5353e57134ffd9fcab6b8714514d4d1/68747470733a2f2f636f6c61622e72657365617263682e676f6f676c652e636f6d2f6173736574732f636f6c61622d62616467652e737667)](https://colab.research.google.com/drive/17ON1LPonnXAtLaZEebnOktstB_1cJJmh?usp=sharing)
## 📖 Documentation
@@ -35,7 +36,7 @@ The documentation for embedchain can be found at [docs.embedchain.ai](https://do
## 💻 Usage
Embedchain empowers you to create chatbot models similar to ChatGPT, using your own evolving dataset.
Embedchain empowers you to create ChatGPT like apps, on your own dynamic dataset.
### Data Types Supported
@@ -45,7 +46,9 @@ Embedchain empowers you to create chatbot models similar to ChatGPT, using your
* Sitemap
* Doc file
* Code documentation website loader
* Notion
* Notion and many more.
You can find the full list of data types on [our documentation](https://docs.embedchain.ai/data-sources/csv).
### Queries
@@ -62,7 +65,7 @@ elon_bot = App()
# Embed online resources
elon_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
elon_bot.add("https://www.forbes.com/profile/elon-musk")
elon_bot.add("https://www.youtube.com/watch?v=MxZpaJK74Y4")
elon_bot.add("https://www.youtube.com/watch?v=RcYjXbSJBN8")
# Query the bot
elon_bot.query("How many companies does Elon Musk run and name those?")
@@ -86,7 +89,7 @@ If you utilize this repository, please consider citing it with:
```
@misc{embedchain,
author = {Taranjeet Singh},
author = {Taranjeet Singh, Deshraj Yadav},
title = {Embedchain: Framework to easily create LLM powered bots over any dataset},
year = {2023},
publisher = {GitHub},
+163 -88
View File
@@ -4,108 +4,122 @@ title: '📱 App types'
## App Types
We have three types of App.
Embedchain supports a variety of LLMs, embedding functions/models and vector databases.
### App
Our app gives you full control over which components you want to use, you can mix and match them to your hearts content.
<Tip>
Out of the box, if you just use `app = App()`, Embedchain uses what we believe to be the best configuration available. This might include paid/proprietary components. Currently, this is
* LLM: OpenAi (gpt-3.5-turbo)
* Embedder: OpenAi (text-embedding-ada-002)
* Database: ChromaDB
</Tip>
### LLM
#### Choosing an LLM
The following LLM providers are supported by Embedchain:
- OPENAI
- ANTHPROPIC
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
- LLAMA2
- JINA
- COHERE
You can choose one by importing it from `embedchain.llm`. E.g.:
```python
from embedchain import App
app = App()
from embedchain.llm.llama2 import Llama2Llm
app = App(llm=Llama2Llm())
```
- `App` uses OpenAI's model, so these are paid models. 💸 You will be charged for embedding model usage and LLM usage.
- `App` uses OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- `App` is opinionated. It uses the best embedding model and LLM on the market.
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
#### Configuration
The LLMs can be configured by passing an LlmConfig object.
The config options can be found [here](/advanced/query_configuration#llmconfig)
```python
import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
from embedchain import App
from embedchain.llm.llama2 import Llama2Llm
from embedchain.config import LlmConfig
app = App(llm=Llama2Llm(), llm_config=LlmConfig(number_documents=3, temperature=0))
```
### Llama2App
### Embedder
#### Choosing an Embedder
The following providers for embedding functions are supported by Embedchain:
- OPENAI
- HUGGING_FACE
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
You can choose one by importing it from `embedchain.embedder`. E.g.:
```python
import os
from embedchain import Llama2App
os.environ['REPLICATE_API_TOKEN'] = "REPLICATE API TOKEN"
zuck_bot = Llama2App()
# Embed your data
zuck_bot.add("https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
zuck_bot.add("https://en.wikipedia.org/wiki/Mark_Zuckerberg")
# Nice, your bot is ready now. Start asking questions to your bot.
zuck_bot.query("Who is Mark Zuckerberg?")
# Answer: Mark Zuckerberg is an American internet entrepreneur and business magnate. He is the co-founder and CEO of Facebook. Born in 1984, he dropped out of Harvard University to focus on his social media platform, which has since grown to become one of the largest and most influential technology companies in the world.
# Enable web search for your bot
zuck_bot.online = True # enable internet access for the bot
zuck_bot.query("Who owns the new threads app and when it was founded?")
# Answer: Based on the context provided, the new Threads app is owned by Meta, the parent company of Facebook, Instagram, and WhatsApp.
```
- `Llama2App` uses Replicate's LLM model, so these are paid models. You can get the `REPLICATE_API_TOKEN` by registering on [their website](https://replicate.com/account).
- `Llama2App` uses OpenAI's embedding model to create embeddings for chunks. Make sure that you have an OpenAI account and an API key. If you don't have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
### OpenSourceApp
```python
from embedchain import OpenSourceApp
app = OpenSourceApp()
```
- `OpenSourceApp` uses open source embedding and LLM model. It uses `all-MiniLM-L6-v2` from Sentence Transformers library as the embedding model and `gpt4all` as the LLM.
- Here there is no need to setup any api keys. You just need to install embedchain package and these will get automatically installed. 📦
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app. 📚
- `OpenSourceApp` is opinionated. It uses the best open source embedding model and LLM on the market.
- extra dependencies are required for this app type. Install them with `pip install --upgrade embedchain[opensource]`.
### CustomApp
```python
from embedchain import CustomApp
from embedchain.config import (CustomAppConfig, ElasticsearchDBConfig,
EmbedderConfig, LlmConfig)
from embedchain import App
from embedchain.embedder.vertexai import VertexAiEmbedder
from embedchain.llm.vertex_ai import VertexAiLlm
from embedchain.models import EmbeddingFunctions, Providers
from embedchain.vectordb.elasticsearch import Elasticsearch
# short
app = CustomApp(llm=VertexAiLlm(), db=Elasticsearch(), embedder=VertexAiEmbedder())
# with configs
app = CustomApp(
config=CustomAppConfig(log_level="INFO"),
llm=VertexAiLlm(config=LlmConfig(number_documents=5)),
db=Elasticsearch(config=ElasticsearchDBConfig(es_url="...")),
embedder=VertexAiEmbedder(config=EmbedderConfig()),
)
app = App(embedder=VertexAiEmbedder())
```
- `CustomApp` is not opinionated.
- Configuration required. It's for advanced users who want to mix and match different embedding models and LLMs.
- while it's doing that, it's still providing abstractions by allowing you to import Classes from `embedchain.llm`, `embedchain.vectordb`, and `embedchain.embedder`.
- paid and free/open source providers included.
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app. 📚
- Following providers are available for an LLM
- OPENAI
- ANTHPROPIC
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
- LLAMA2
- Following embedding functions are available for an embedding function
- OPENAI
- HUGGING_FACE
- VERTEX_AI
- GPT4ALL
- AZURE_OPENAI
#### Configuration
The LLMs can be configured by passing an EmbedderConfig object.
```python
from embedchain import App
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.config import EmbedderConfig
app = App(embedder=OpenAiEmbedder(), embedder_config=EmbedderConfig(model="text-embedding-ada-002"))
```
<Tip>
You can also pass an `LlmConfig` instance directly to the `query` or `chat` method.
This creates a temporary config for that request alone, so you could, for example, use a different model (from the same provider) or get more context documents for a specific query.
</Tip>
### Vector Database
#### Choosing a Vector Database
The following vector databases are supported by Embedchain:
- ChromaDB
- Elasticsearch
You can choose one by importing it from `embedchain.vectordb`. E.g.:
```python
from embedchain import App
from embedchain.vectordb.elasticsearch import ElasticsearchDB
app = App(db=ElasticsearchDB())
```
#### Configuration
The vector databases can be configured by passing a specific config object.
These vary greatly between the different vector databases.
```python
from embedchain import App
from embedchain.vectordb.elasticsearch import ElasticsearchDB
from embedchain.config import ElasticsearchDBConfig
app = App(db=ElasticsearchDB(), db_config=ElasticsearchDBConfig())
```
### PersonApp
@@ -123,18 +137,79 @@ import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"
```
#### Compatibility with other apps
### Full Configuration Examples
Embedchain previously offered fully configured classes, namely `App`, `OpenSourceApp`, `CustomApp` and `Llama2App`.
We deprecated these apps. The reason for this decision was that it was hard to switch from to a different LLM, embedder or vector db, if you one day decided that that's what you want to do.
The new app allows drop-in replacements, such as changing `App(llm=OpenAiLlm())` to `App(llm=Llama2Llm())`.
To make the switch to our new, fully configurable app easier, we provide you with full examples for what the old classes would look like implemented as a new app.
You can swap these in, and if you decide you want to try a different model one day, you don't have to rewrite your whole bot.
#### App
App without any configuration is still using the best options available, so you can keep using:
```python
from embedchain import App
app = App()
```
#### OpenSourceApp
Use this snippet to run an open source app.
```python
from embedchain import App
from embedchain.llm.gpt4all import GPT4ALLLlm
from embedchain.embedder.gpt4all import GPT4AllEmbedder
from embedchain.vectordb.chroma import ChromaDB
app = App(llm=GPT4ALLLlm(), embedder=GPT4AllEmbedder(), db=ChromaDB())
```
#### Llama2App
```python
from embedchain import App
from embedchain.llm.llama2 import Llama2Llm
app = App(llm=Llama2Llm())
```
#### CustomApp
Every app is a custom app now.
If you were previously using a `CustomApp`, you can now just change it to `App`.
Here's one example, what you could do if we combined everything shown on this page.
```python
from embedchain import App
from embedchain.config import ElasticsearchDBConfig, EmbedderConfig, LlmConfig
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.llm.llama2 import Llama2Llm
from embedchain.vectordb.elasticsearch import ElasticsearchDB
app = App(
llm=Llama2Llm(),
llm_config=LlmConfig(number_documents=3, temperature=0),
embedder=OpenAiEmbedder(),
embedder_config=EmbedderConfig(model="text-embedding-ada-002"),
db=ElasticsearchDB(),
db_config=ElasticsearchDBConfig(),
)
```
### Compatibility with other apps
- If there is any other app instance in your script or app, you can change the import as
```python
from embedchain import App as EmbedChainApp
from embedchain import OpenSourceApp as EmbedChainOSApp
from embedchain import PersonApp as EmbedChainPersonApp
# or
from embedchain import App as ECApp
from embedchain import OpenSourceApp as ECOSApp
from embedchain import PersonApp as ECPApp
```
-160
View File
@@ -1,160 +0,0 @@
---
title: '📋 Supported data formats'
---
## Automatic data type detection
The add method automatically tries to detect the data_type, based on your input for the source argument. So `app.add('https://www.youtube.com/watch?v=dQw4w9WgXcQ')` is enough to embed a YouTube video.
This detection is implemented for all formats. It is based on factors such as whether it's a URL, a local file, the source data type, etc.
### Debugging automatic detection
Set `log_level=DEBUG` (in [AppConfig](http://localhost:3000/advanced/query_configuration#appconfig)) and make sure it's working as intended.
Otherwise, you will not know when, for instance, an invalid filepath is interpreted as raw text instead.
### Forcing a data type
To omit any issues with the data type detection, you can **force** a data_type by adding it as a `add` method argument.
The examples below show you the keyword to force the respective `data_type`.
Forcing can also be used for edge cases, such as interpreting a sitemap as a web_page, for reading its raw text instead of following links.
## Remote Data Types
<Tip>
**Use local files in remote data types**
Some data_types are meant for remote content and only work with URLs.
You can pass local files by formatting the path using the `file:` [URI scheme](https://en.wikipedia.org/wiki/File_URI_scheme), e.g. `file:///info.pdf`.
</Tip>
### Youtube video
To add any youtube video to your app, use the data_type (first argument to `.add()` method) as `youtube_video`. Eg:
```python
app.add('a_valid_youtube_url_here', data_type='youtube_video')
```
### PDF file
To add any pdf file, use the data_type as `pdf_file`. Eg:
```python
app.add('a_valid_url_where_pdf_file_can_be_accessed', data_type='pdf_file')
```
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('a_valid_web_page_url', data_type='web_page')
```
### Sitemap
Add all web pages from an xml-sitemap. Filters non-text files. Use the data_type as `sitemap`. Eg:
```python
app.add('https://example.com/sitemap.xml', data_type='sitemap')
```
### Doc file
To add any doc/docx file, use the data_type as `docx`. `docx` allows remote urls and conventional file paths. Eg:
```python
app.add('https://example.com/content/intro.docx', data_type="docx")
app.add('content/intro.docx', data_type="docx")
```
### CSV file
To add any csv file, use the data_type as `csv`. `csv` allows remote urls and conventional file paths. Headers are included for each line, so if you have an `age` column, `18` will be added as `age: 18`. Eg:
```python
app.add('https://example.com/content/sheet.csv', data_type="csv")
app.add('content/sheet.csv', data_type="csv")
```
Note: There is a size limit allowed for csv file beyond which it can throw error. This limit is set by the LLMs. Please consider chunking large csv files into smaller csv files.
### Code documentation website loader
To add any code documentation website as a loader, use the data_type as `docs_site`. Eg:
```python
app.add("https://docs.embedchain.ai/", data_type="docs_site")
```
### Notion
To use notion you must install the extra dependencies with `pip install --upgrade embedchain[notion]`.
To load a notion page, use the data_type as `notion`. Since it is hard to automatically detect, forcing this is advised.
The next argument must **end** with the `notion page id`. The id is a 32-character string. Eg:
```python
app.add("cfbc134ca6464fc980d0391613959196", "notion")
app.add("my-page-cfbc134ca6464fc980d0391613959196", "notion")
app.add("https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196", "notion")
```
### Mdx file
To add any mdx file to your app, use the data_type (first argument to `.add()` method) as `mdx`. Note that this supports support mdx file present on machine, so this should be a file path. Eg:
```python
app.add('path/to/file.mdx', data_type='mdx')
```
## Local Data Types
### Text
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('Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.', data_type='text')
```
Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit.
### QnA pair
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```python
app.add(("Question", "Answer"), data_type="qna_pair")
```
## 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("https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
```
You can reuse the local index with the same code, but without adding new documents:
```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.
+1 -1
View File
@@ -70,6 +70,6 @@ app.reset()
Counts the number of embeddings (chunks) in the database.
```python
print(app.count())
print(app.db.count())
# returns: 481
```
+53 -5
View File
@@ -2,7 +2,7 @@
title: '💾 Vector Database'
---
We support `Chroma` and `Elasticsearch` as two vector database.
We support `Chroma`, `Elasticsearch` and `OpenSearch` as vector databases.
`Chroma` is used as a default database.
## Elasticsearch
@@ -22,13 +22,13 @@ Please note that the key needs certain privileges. For testing you can just togg
2. Load the app
```python
from embedchain import CustomApp
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.elasticsearch import ElasticsearchDB
es_app = CustomApp(
llm=OpenAILlm(),
embedder=OpenAiEmbedder(),
embedder=OpenAIEmbedder(),
db=ElasticsearchDB(),
)
```
@@ -45,7 +45,7 @@ import os
from embedchain import CustomApp
from embedchain.config import CustomAppConfig, ElasticsearchDBConfig
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.elasticsearch import ElasticsearchDB
@@ -61,10 +61,58 @@ es_config = ElasticsearchDBConfig(
es_app = CustomApp(
config=CustomAppConfig(log_level="INFO"),
llm=OpenAILlm(),
embedder=OpenAiEmbedder(),
embedder=OpenAIEmbedder(),
db=ElasticsearchDB(config=es_config),
)
```
3. This should log your connection details to the console.
4. Alternatively to a URL, you `ElasticsearchDBConfig` accepts `es_url` as a list of nodes url with different hosts and ports.
5. Additionally we can pass named parameters supported by Python Elasticsearch client.
## OpenSearch 🔍
To use OpenSearch as a vector database with a CustomApp, follow these simple steps:
1. Set the `OPENAI_API_KEY` environment variable:
```
OPENAI_API_KEY=sk-xxxx
```
2. Define the OpenSearch configuration in your Python code:
```python
from embedchain import CustomApp
from embedchain.config import OpenSearchDBConfig
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.opensearch import OpenSearchDB
opensearch_url = "https://localhost:9200"
http_auth = ("username", "password")
db_config = OpenSearchDBConfig(
opensearch_url=opensearch_url,
http_auth=http_auth,
collection_name="embedchain-app",
use_ssl=True,
timeout=30,
)
db = OpenSearchDB(config=db_config)
```
2. Instantiate the app and add data:
```python
app = CustomApp(llm=OpenAILlm(), embedder=OpenAIEmbedder(), db=db)
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
app.add("https://www.forbes.com/profile/elon-musk")
app.add("https://www.britannica.com/biography/Elon-Musk")
```
3. You're all set! Start querying using the following command:
```python
app.query("What is the net worth of Elon Musk?")
```
+18
View File
@@ -0,0 +1,18 @@
---
title: 🤝 Connect with Us
---
We believe in building a vibrant and supportive community around embedchain. There are various channels through which you can connect with us, stay updated, and contribute to the ongoing discussions:
* Slack: Our Slack workspace provides a platform for more structured discussions and channels dedicated to different topics. Feel free to jump in and start contributing. [Join Slack](https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw).
* Discord: Join our Discord server to engage in real-time conversations with the community members and the project maintainers. It’s a great place to seek help and discuss anything related to the project. [Join Discord](https://discord.gg/CUU9FPhRNt).
* Twitter: Follow us on Twitter for the latest news, announcements, and highlights from our community. It’s also a quick way to reach out to us. [Follow @embedchain](https://twitter.com/embedchain).
* LinkedIn: Connect with us on LinkedIn to stay updated on official announcements, job openings, and professional networking opportunities within our community. [Follow Our Page](https://www.linkedin.com/company/embedchain/).
* Newsletter: Subscribe to our newsletter for a curated list of project updates, community contributions, and upcoming events. It’s a compact way to stay in the loop with what’s happening in our community. [Subscribe Now](https://embedchain.substack.com/).
We look forward to connecting with you and seeing how we can create amazing things together!
-1
View File
@@ -42,7 +42,6 @@ embedchain is built on the following stack:
### Maintainer
- Deshraj Yadav ([@deshrajdry](https://twitter.com/taranjeetio))
- [cachho](https://github.com/cachho)
### Citation
+4
View File
@@ -0,0 +1,4 @@
---
title: '📋 Guidelines'
url: https://github.com/embedchain/embedchain/blob/main/CONTRIBUTING.md
---
+4
View File
@@ -0,0 +1,4 @@
---
title: ' 🟨 Javascript'
url: https://github.com/embedchain/embedchain/tree/main/embedchain-js
---
+4
View File
@@ -0,0 +1,4 @@
---
title: '🐍 Python'
url: https://github.com/embedchain/embedchain
---
+14
View File
@@ -0,0 +1,14 @@
---
title: 'CSV'
---
### CSV file
To add any csv file, use the data_type as `csv`. `csv` allows remote urls and conventional file paths. Headers are included for each line, so if you have an `age` column, `18` will be added as `age: 18`. Eg:
```python
app.add('https://example.com/content/sheet.csv', data_type="csv")
app.add('content/sheet.csv', data_type="csv")
```
Note: There is a size limit allowed for csv file beyond which it can throw error. This limit is set by the LLMs. Please consider chunking large csv files into smaller csv files.
+54
View File
@@ -0,0 +1,54 @@
---
title: 'Data Type Handling'
---
## Automatic data type detection
The add method automatically tries to detect the data_type, based on your input for the source argument. So `app.add('https://www.youtube.com/watch?v=dQw4w9WgXcQ')` is enough to embed a YouTube video.
This detection is implemented for all formats. It is based on factors such as whether it's a URL, a local file, the source data type, etc.
### Debugging automatic detection
Set `log_level=DEBUG` (in [AppConfig](http://localhost:3000/advanced/query_configuration#appconfig)) and make sure it's working as intended.
Otherwise, you will not know when, for instance, an invalid filepath is interpreted as raw text instead.
### Forcing a data type
To omit any issues with the data type detection, you can **force** a data_type by adding it as a `add` method argument.
The examples below show you the keyword to force the respective `data_type`.
Forcing can also be used for edge cases, such as interpreting a sitemap as a web_page, for reading its raw text instead of following links.
## Remote Data Types
<Tip>
**Use local files in remote data types**
Some data_types are meant for remote content and only work with URLs.
You can pass local files by formatting the path using the `file:` [URI scheme](https://en.wikipedia.org/wiki/File_URI_scheme), e.g. `file:///info.pdf`.
</Tip>
## 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("https://www.youtube.com/watch?v=3qHkcs3kG44")
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
```
You can reuse the local index with the same code, but without adding new documents:
```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?"))
```
+11
View File
@@ -0,0 +1,11 @@
---
title: 'Code Documentation'
---
### Code documentation
To add any code documentation website as a loader, use the data_type as `docs_site`. Eg:
```python
app.add("https://docs.embedchain.ai/", data_type="docs_site")
```
+12
View File
@@ -0,0 +1,12 @@
---
title: 'Docx File'
---
### Docx file
To add any doc/docx file, use the data_type as `docx`. `docx` allows remote urls and conventional file paths. Eg:
```python
app.add('https://example.com/content/intro.docx', data_type="docx")
app.add('content/intro.docx', data_type="docx")
```
@@ -1,16 +1,15 @@
---
title: '➕ Adding Data'
title: 'How to add data'
---
## Add Dataset
- This step assumes that you have already created an `app` instance by either using `App`, `OpenSourceApp` or `CustomApp`. We are calling our app instance as `naval_chat_bot` 🤖
- This step assumes that you have already created an `App`. We are calling our app instance as `naval_chat_bot` 🤖
- Now use `.add` method to add any dataset.
```python
# naval_chat_bot = App() or
# naval_chat_bot = OpenSourceApp()
naval_chat_bot = App()
# Embed Online Resources
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
+12
View File
@@ -0,0 +1,12 @@
---
title: 'Mdx'
---
### Mdx file
To add any mdx file to your app, use the data_type (first argument to `.add()` method) as `mdx`. Note that this supports support mdx file present on machine, so this should be a file path. Eg:
```python
app.add('path/to/file.mdx', data_type='mdx')
```
+15
View File
@@ -0,0 +1,15 @@
---
title: 'Notion'
---
### Notion
To use notion you must install the extra dependencies with `pip install --upgrade embedchain[notion]`.
To load a notion page, use the data_type as `notion`. Since it is hard to automatically detect, forcing this is advised.
The next argument must **end** with the `notion page id`. The id is a 32-character string. Eg:
```python
app.add("cfbc134ca6464fc980d0391613959196", "notion")
app.add("my-page-cfbc134ca6464fc980d0391613959196", "notion")
app.add("https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196", "notion")
```
+14
View File
@@ -0,0 +1,14 @@
---
title: 'PDF File'
---
### PDF File
To add any pdf file, use the data_type as `pdf_file`. Eg:
```python
app.add('a_valid_url_where_pdf_file_can_be_accessed', data_type='pdf_file')
```
Note that we do not support password protected pdfs.
+11
View File
@@ -0,0 +1,11 @@
---
title: 'QnA Pair'
---
### QnA pair
QnA pair is a local data type. To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```python
app.add(("Question", "Answer"), data_type="qna_pair")
```
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Request New Format'
url: https://forms.gle/gB5La14tjgy4p94dA
---
+11
View File
@@ -0,0 +1,11 @@
---
title: 'Sitemap'
---
### Sitemap
Add all web pages from an xml-sitemap. Filters non-text files. Use the data_type as `sitemap`. Eg:
```python
app.add('https://example.com/sitemap.xml', data_type='sitemap')
```
+13
View File
@@ -0,0 +1,13 @@
---
title: 'Text'
---
### Text
Text is a local data type. 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('Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.', data_type='text')
```
Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit.
+11
View File
@@ -0,0 +1,11 @@
---
title: 'Web page'
---
### Web page
To add any web page, use the data_type as `web_page`. Eg:
```python
app.add('a_valid_web_page_url', data_type='web_page')
```
+13
View File
@@ -0,0 +1,13 @@
---
title: 'XML File'
---
### XML file
To add any xml file, use the data_type as `xml`. Eg:
```python
app.add('content/data.xml')
```
Note: Only the text content of the xml file will be added to the app. The tags will be ignored.
+12
View File
@@ -0,0 +1,12 @@
---
title: 'Youtube Video'
---
### 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('a_valid_youtube_url_here', data_type='youtube_video')
```
+16
View File
@@ -0,0 +1,16 @@
---
title: ❓ Frequently Asked Questions
description: 'Collections of all the frequently asked questions about Embedchain'
---
## How to use GPT-4 as the LLM model
```python
from embedchain import App
from embedchain.config import LlmConfig
app = App()
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
app.query("How many companies does Elon Musk run and name those?", config=LlmConfig(model="gpt-4"))
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

+51
View File
@@ -0,0 +1,51 @@
---
title: '🛠️ LangSmith'
description: 'Integrate with Langsmith to debug and monitor your LLM app'
---
Embedchain now supports integration with [LangSmith](https://www.langchain.com/langsmith).
To use langsmith, you need to do the following steps
1. Have an account on langsmith and keep the environment variables in handy
2. Set the environments variables in your app so that embedchain has context about it.
3. Just use embedchain and everything will be logged to LangSmith, so that you can better test and monitor your application.
Lets cover each step in detail.
* First make sure that you a LangSmith account created and have all the necessary variables handy. LangSmith has a [good documentation](https://docs.smith.langchain.com/) on how to get started with their service.
* Once you have the account setup, we will need the following environment variables
```bash
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
export LANGCHAIN_API_KEY=<your-api-key>
export LANGCHAIN_PROJECT=<your-project> # if not specified, defaults to "default"
```
If you are using Python, you can use the following code to set environment variables
```python
import os
os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'
os.environ['LANGCHAIN_API_KEY'] = <your-api-key>
os.environ['LANGCHAIN_PROJECT] = <your-project>
```
* Now create an app using embedchain and everything will be automatically visible in the LangSmith
```python
from embedchain import App
app = App()
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
app.query("How many companies did Elon found?")
```
* Now the entire log for this will be visible in langsmith.
<img src="/images/langsmith.png"/>
+60 -6
View File
@@ -19,6 +19,10 @@
{
"name": "Discord",
"url": "https://discord.gg/6PzXDgEjG5"
},
{
"name":"Slack",
"url":"https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw"
}
],
"topbarCtaButton": {
@@ -27,28 +31,78 @@
},
"navigation": [
{
"group": "Getting started",
"pages": ["quickstart", "introduction"]
"group": "Get started",
"pages": ["get-start/quickstart", "get-start/introduction", "get-start/faq"]
},
{
"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"]
"pages": ["advanced/app_types", "advanced/interface_types", "advanced/query_configuration", "advanced/configuration", "advanced/testing", "advanced/vector_database"]
},
{
"group": "Data Sources",
"pages": [
"data-sources/how-to-add-data",
"data-sources/data-type-handling",
{
"group": "Supported Data Sources",
"pages": [
"data-sources/csv",
"data-sources/docs-site",
"data-sources/docx",
"data-sources/mdx",
"data-sources/notion",
"data-sources/pdf-file",
"data-sources/qna",
"data-sources/sitemap",
"data-sources/text",
"data-sources/web-page",
"data-sources/youtube-video"
]
},
"data-sources/request-new-format"
]
},
{
"group": "Examples",
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot", "examples/slack_bot", "examples/telegram_bot", "examples/whatsapp_bot", "examples/poe_bot"]
},
{
"group": "Contribution Guidelines",
"pages": ["contribution/dev", "contribution/docs"]
"group": "Community",
"pages": [
"community/connect-with-us",
"community/showcase"
]
},
{
"group": "Integration",
"pages": ["integration/langsmith"]
},
{
"group": "Contribute",
"pages": [
"contribution/guidelines",
"contribution/dev",
"contribution/docs",
"contribution/python",
"contribution/javascript"
]
},
{
"group": "Product",
"pages": [
"product/release-notes"
]
}
],
"footerSocials": {
"twitter": "https://twitter.com/embedchain",
"github": "https://github.com/embedchain/embedchain",
"linkedin": "https://www.linkedin.com/company/embedchain",
"website": "https://embedchain.ai"
"website": "https://embedchain.ai",
"slack":"https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw"
},
"backgroundImage": "/background.png",
"isWhiteLabeled": true
+4
View File
@@ -0,0 +1,4 @@
---
title: ' 📜 Release Notes'
url: https://github.com/embedchain/embedchain/releases
---
+2 -2
View File
@@ -2,10 +2,10 @@ import importlib.metadata
__version__ = importlib.metadata.version(__package__ or __name__)
from embedchain.apps.App import App # noqa: F401
from embedchain.apps.app import App # noqa: F401
from embedchain.apps.custom_app import CustomApp # noqa: F401
from embedchain.apps.Llama2App import Llama2App # noqa: F401
from embedchain.apps.open_source_app import OpenSourceApp # noqa: F401
from embedchain.apps.PersonApp import (PersonApp, # noqa: F401
from embedchain.apps.person_app import (PersonApp, # noqa: F401
PersonOpenSourceApp)
from embedchain.vectordb.chroma import ChromaDB # noqa: F401
-54
View File
@@ -1,54 +0,0 @@
from typing import Optional
from embedchain.config import (AppConfig, BaseEmbedderConfig, BaseLlmConfig,
ChromaDbConfig)
from embedchain.embedchain import EmbedChain
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.chroma import ChromaDB
@register_deserializable
class App(EmbedChain):
"""
The EmbedChain app in it's simplest and most straightforward form.
An opinionated choice of LLM, vector database and embedding model.
Methods:
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
"""
def __init__(
self,
config: AppConfig = None,
llm_config: BaseLlmConfig = None,
chromadb_config: Optional[ChromaDbConfig] = None,
system_prompt: Optional[str] = None,
):
"""
Initialize a new `CustomApp` instance. You only have a few choices to make.
:param config: Config for the app instance.
This is the most basic configuration, that does not fall into the LLM, database or embedder category,
defaults to None
:type config: AppConfig, optional
:param llm_config: Allows you to configure the LLM, e.g. how many documents to return,
example: `from embedchain.config import LlmConfig`, defaults to None
:type llm_config: BaseLlmConfig, optional
:param chromadb_config: Allows you to configure the vector database,
example: `from embedchain.config import ChromaDbConfig`, defaults to None
:type chromadb_config: Optional[ChromaDbConfig], optional
:param system_prompt: System prompt that will be provided to the LLM as such, defaults to None
:type system_prompt: Optional[str], optional
"""
if config is None:
config = AppConfig()
llm = OpenAILlm(config=llm_config)
embedder = OpenAiEmbedder(config=BaseEmbedderConfig(model="text-embedding-ada-002"))
database = ChromaDB(config=chromadb_config)
super().__init__(config, llm, db=database, embedder=embedder, system_prompt=system_prompt)
+15 -10
View File
@@ -1,15 +1,14 @@
import logging
from typing import Optional
from embedchain.apps.custom_app import CustomApp
from embedchain.apps.app import App
from embedchain.config import CustomAppConfig
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.llama2 import Llama2Llm
from embedchain.vectordb.chroma import ChromaDB
@register_deserializable
class Llama2App(CustomApp):
class Llama2App(App):
"""
The EmbedChain Llama2App class.
@@ -17,17 +16,23 @@ class Llama2App(CustomApp):
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
.. deprecated:: 0.0.64
Use `App` instead.
"""
def __init__(self, config: CustomAppConfig = None, system_prompt: Optional[str] = None):
"""
.. deprecated:: 0.0.64
Use `App` instead.
:param config: CustomAppConfig instance to load as configuration. Optional.
:param system_prompt: System prompt string. Optional.
"""
if config is None:
config = CustomAppConfig()
super().__init__(
config=config, llm=Llama2Llm(), db=ChromaDB(), embedder=OpenAiEmbedder(), system_prompt=system_prompt
logging.warning(
"DEPRECATION WARNING: Please use `App` instead of `Llama2App`. "
"`Llama2App` will be removed in a future release. "
"Please refer to https://docs.embedchain.ai/advanced/app_types#llama2app for instructions."
)
super().__init__(config=config, llm=Llama2Llm(), system_prompt=system_prompt)
+125
View File
@@ -0,0 +1,125 @@
import logging
from typing import Optional
from embedchain.config import (AppConfig, BaseEmbedderConfig, BaseLlmConfig,
ChromaDbConfig)
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.embedchain import EmbedChain
from embedchain.embedder.base import BaseEmbedder
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
from embedchain.llm.openai import OpenAILlm
from embedchain.vectordb.base import BaseVectorDB
from embedchain.vectordb.chroma import ChromaDB
@register_deserializable
class App(EmbedChain):
"""
The EmbedChain app in it's simplest and most straightforward form.
An opinionated choice of LLM, vector database and embedding model.
Methods:
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
"""
def __init__(
self,
config: Optional[AppConfig] = None,
llm: BaseLlm = None,
llm_config: Optional[BaseLlmConfig] = None,
db: BaseVectorDB = None,
db_config: Optional[BaseVectorDbConfig] = None,
embedder: BaseEmbedder = None,
embedder_config: Optional[BaseEmbedderConfig] = None,
chromadb_config: Optional[ChromaDbConfig] = None,
system_prompt: Optional[str] = None,
):
"""
Initialize a new `App` instance.
:param config: Config for the app instance., defaults to None
:type config: Optional[AppConfig], optional
:param llm: LLM Class instance. example: `from embedchain.llm.openai import OpenAILlm`, defaults to OpenAiLlm
:type llm: BaseLlm, optional
:param llm_config: Allows you to configure the LLM, e.g. how many documents to return,
example: `from embedchain.config import LlmConfig`, defaults to None
:type llm_config: Optional[BaseLlmConfig], optional
:param db: The database to use for storing and retrieving embeddings,
example: `from embedchain.vectordb.chroma_db import ChromaDb`, defaults to ChromaDb
:type db: BaseVectorDB, optional
:param db_config: Allows you to configure the vector database,
example: `from embedchain.config import ChromaDbConfig`, defaults to None
:type db_config: Optional[BaseVectorDbConfig], optional
:param embedder: The embedder (embedding model and function) use to calculate embeddings.
example: `from embedchain.embedder.gpt4all_embedder import GPT4AllEmbedder`, defaults to OpenAIEmbedder
:type embedder: BaseEmbedder, optional
:param embedder_config: Allows you to configure the Embedder.
example: `from embedchain.config import BaseEmbedderConfig`, defaults to None
:type embedder_config: Optional[BaseEmbedderConfig], optional
:param chromadb_config: Deprecated alias of `db_config`, defaults to None
:type chromadb_config: Optional[ChromaDbConfig], optional
:param system_prompt: System prompt that will be provided to the LLM as such, defaults to None
:type system_prompt: Optional[str], optional
:raises TypeError: LLM, database or embedder or their config is not a valid class instance.
"""
# Overwrite deprecated arguments
if chromadb_config:
logging.warning(
"DEPRECATION WARNING: Please use `db_config` argument instead of `chromadb_config`."
"`chromadb_config` will be removed in a future release."
)
db_config = chromadb_config
# Type check configs
if config and not isinstance(config, AppConfig):
raise TypeError(
"Config is not a `AppConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if llm_config and not isinstance(llm_config, BaseLlmConfig):
raise TypeError(
"`llm_config` is not a `BaseLlmConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if db_config and not isinstance(db_config, BaseVectorDbConfig):
raise TypeError(
"`db_config` is not a `BaseVectorDbConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if embedder_config and not isinstance(embedder_config, BaseEmbedderConfig):
raise TypeError(
"`embedder_config` is not a `BaseEmbedderConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
# Assign defaults
if config is None:
config = AppConfig()
if llm is None:
llm = OpenAILlm(config=llm_config)
if db is None:
db = ChromaDB(config=db_config)
if embedder is None:
embedder = OpenAIEmbedder(config=embedder_config)
# Type check assignments
if not isinstance(llm, BaseLlm):
raise TypeError(
"LLM is not a `BaseLlm` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(db, BaseVectorDB):
raise TypeError(
"Database is not a `BaseVectorDB` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(embedder, BaseEmbedder):
raise TypeError(
"Embedder is not a `BaseEmbedder` instance. "
"Please make sure the type is right and that you are passing an instance."
)
super().__init__(config, llm=llm, db=db, embedder=embedder, system_prompt=system_prompt)
+14 -34
View File
@@ -1,7 +1,8 @@
import logging
from typing import Optional
from embedchain.apps.app import App
from embedchain.config import CustomAppConfig
from embedchain.embedchain import EmbedChain
from embedchain.embedder.base import BaseEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
@@ -9,7 +10,7 @@ from embedchain.vectordb.base import BaseVectorDB
@register_deserializable
class CustomApp(EmbedChain):
class CustomApp(App):
"""
Embedchain's custom app allows for most flexibility.
@@ -19,6 +20,9 @@ class CustomApp(EmbedChain):
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
.. deprecated:: 0.0.64
Use `App` instead.
"""
def __init__(
@@ -32,6 +36,9 @@ class CustomApp(EmbedChain):
"""
Initialize a new `CustomApp` instance. You have to choose a LLM, database and embedder.
.. deprecated:: 0.0.64
Use `App` instead.
:param config: Config for the app instance. This is the most basic configuration,
that does not fall into the LLM, database or embedder category, defaults to None
:type config: Optional[CustomAppConfig], optional
@@ -48,36 +55,9 @@ class CustomApp(EmbedChain):
:raises ValueError: LLM, database or embedder has not been defined.
:raises TypeError: LLM, database or embedder is not a valid class instance.
"""
# Config is not required, it has a default
if config is None:
config = CustomAppConfig()
if llm is None:
raise ValueError("LLM must be provided for custom app. Please import from `embedchain.llm`.")
if db is None:
raise ValueError("Database must be provided for custom app. Please import from `embedchain.vectordb`.")
if embedder is None:
raise ValueError("Embedder must be provided for custom app. Please import from `embedchain.embedder`.")
if not isinstance(config, CustomAppConfig):
raise TypeError(
"Config is not a `CustomAppConfig` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(llm, BaseLlm):
raise TypeError(
"LLM is not a `BaseLlm` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(db, BaseVectorDB):
raise TypeError(
"Database is not a `BaseVectorDB` instance. "
"Please make sure the type is right and that you are passing an instance."
)
if not isinstance(embedder, BaseEmbedder):
raise TypeError(
"Embedder is not a `BaseEmbedder` instance. "
"Please make sure the type is right and that you are passing an instance."
)
logging.warning(
"DEPRECATION WARNING: Please use `App` instead of `CustomApp`. "
"`CustomApp` will be removed in a future release. "
"Please refer to https://docs.embedchain.ai/advanced/app_types#opensourceapp for instructions."
)
super().__init__(config=config, llm=llm, db=db, embedder=embedder, system_prompt=system_prompt)
+22 -29
View File
@@ -1,9 +1,9 @@
import logging
from typing import Optional
from embedchain.config import (BaseEmbedderConfig, BaseLlmConfig,
ChromaDbConfig, OpenSourceAppConfig)
from embedchain.embedchain import EmbedChain
from embedchain.apps.app import App
from embedchain.config import (BaseLlmConfig, ChromaDbConfig,
OpenSourceAppConfig)
from embedchain.embedder.gpt4all import GPT4AllEmbedder
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.gpt4all import GPT4ALLLlm
@@ -13,7 +13,7 @@ gpt4all_model = None
@register_deserializable
class OpenSourceApp(EmbedChain):
class OpenSourceApp(App):
"""
The embedchain Open Source App.
Comes preconfigured with the best open source LLM, embedding model, database.
@@ -22,6 +22,9 @@ class OpenSourceApp(EmbedChain):
add(source, data_type): adds the data from the given URL to the vector db.
query(query): finds answer to the given query using vector database and LLM.
chat(query): finds answer to the given query using vector database and LLM, with conversation history.
.. deprecated:: 0.0.64
Use `App` instead.
"""
def __init__(
@@ -36,6 +39,9 @@ class OpenSourceApp(EmbedChain):
Since it's opinionated you don't have to choose a LLM, database and embedder.
However, you can configure those.
.. deprecated:: 0.0.64
Use `App` instead.
:param config: Config for the app instance. This is the most basic configuration,
that does not fall into the LLM, database or embedder category, defaults to None
:type config: OpenSourceAppConfig, optional
@@ -50,29 +56,16 @@ class OpenSourceApp(EmbedChain):
:type system_prompt: Optional[str], optional
:raises TypeError: `OpenSourceAppConfig` or `LlmConfig` invalid.
"""
logging.info("Loading open source embedding model. This may take some time...") # noqa:E501
if not config:
config = OpenSourceAppConfig()
logging.warning(
"DEPRECATION WARNING: Please use `App` instead of `OpenSourceApp`."
"`OpenSourceApp` will be removed in a future release."
"Please refer to https://docs.embedchain.ai/advanced/app_types#customapp for instructions."
)
if not isinstance(config, OpenSourceAppConfig):
raise TypeError(
"OpenSourceApp needs a OpenSourceAppConfig passed to it. "
"You can import it with `from embedchain.config import OpenSourceAppConfig`"
)
if not llm_config:
llm_config = BaseLlmConfig(model="orca-mini-3b.ggmlv3.q4_0.bin")
elif not isinstance(llm_config, BaseLlmConfig):
raise TypeError(
"The LlmConfig passed to OpenSourceApp is invalid. "
"You can import it with `from embedchain.config import LlmConfig`"
)
elif not llm_config.model:
llm_config.model = "orca-mini-3b.ggmlv3.q4_0.bin"
llm = GPT4ALLLlm(config=llm_config)
embedder = GPT4AllEmbedder(config=BaseEmbedderConfig(model="all-MiniLM-L6-v2"))
logging.error("Successfully loaded open source embedding model.")
database = ChromaDB(config=chromadb_config)
super().__init__(config, llm=llm, db=database, embedder=embedder, system_prompt=system_prompt)
super().__init__(
config=config,
llm=GPT4ALLLlm(config=llm_config),
db=ChromaDB(config=chromadb_config),
embedder=GPT4AllEmbedder(),
system_prompt=system_prompt,
)
@@ -1,6 +1,6 @@
from string import Template
from embedchain.apps.App import App
from embedchain.apps.app import App
from embedchain.apps.open_source_app import OpenSourceApp
from embedchain.config import BaseLlmConfig
from embedchain.config.apps.base_app_config import BaseAppConfig
+2 -2
View File
@@ -2,7 +2,7 @@ from typing import Any
from embedchain import CustomApp
from embedchain.config import AddConfig, CustomAppConfig, LlmConfig
from embedchain.embedder.openai import OpenAiEmbedder
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.helper.json_serializable import (JSONSerializable,
register_deserializable)
from embedchain.llm.openai import OpenAILlm
@@ -12,7 +12,7 @@ from embedchain.vectordb.chroma import ChromaDB
@register_deserializable
class BaseBot(JSONSerializable):
def __init__(self):
self.app = CustomApp(config=CustomAppConfig(), llm=OpenAILlm(), db=ChromaDB(), embedder=OpenAiEmbedder())
self.app = CustomApp(config=CustomAppConfig(), llm=OpenAILlm(), db=ChromaDB(), embedder=OpenAIEmbedder())
def add(self, data: Any, config: AddConfig = None):
"""
+12 -4
View File
@@ -10,7 +10,7 @@ class BaseChunker(JSONSerializable):
self.text_splitter = text_splitter
self.data_type = None
def create_chunks(self, loader, src):
def create_chunks(self, loader, src, app_id=None):
"""
Loads data and chunks it.
@@ -18,13 +18,18 @@ class BaseChunker(JSONSerializable):
the raw data.
:param src: The data to be handled by the loader. Can be a URL for
remote sources or local content for local loaders.
:param app_id: App id used to generate the doc_id.
"""
documents = []
ids = []
chunk_ids = []
idMap = {}
data_result = loader.load_data(src)
data_records = data_result["data"]
doc_id = data_result["doc_id"]
# Prefix app_id in the document id if app_id is not None to
# distinguish between different documents stored in the same
# elasticsearch or opensearch index
doc_id = f"{app_id}--{doc_id}" if app_id is not None else doc_id
metadatas = []
for data in data_records:
content = data["content"]
@@ -41,12 +46,12 @@ class BaseChunker(JSONSerializable):
chunk_id = hashlib.sha256((chunk + url).encode()).hexdigest()
if idMap.get(chunk_id) is None:
idMap[chunk_id] = True
ids.append(chunk_id)
chunk_ids.append(chunk_id)
documents.append(chunk)
metadatas.append(meta_data)
return {
"documents": documents,
"ids": ids,
"ids": chunk_ids,
"metadatas": metadatas,
"doc_id": doc_id,
}
@@ -66,3 +71,6 @@ class BaseChunker(JSONSerializable):
self.data_type = data_type
# TODO: This should be done during initialization. This means it has to be done in the child classes.
def get_word_count(self, documents):
return sum([len(document.split(" ")) for document in documents])
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+64
View File
@@ -0,0 +1,64 @@
import hashlib
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.add_config import ChunkerConfig
class ImagesChunker(BaseChunker):
"""Chunker for an Image."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len)
image_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(image_splitter)
def create_chunks(self, loader, src, app_id=None):
"""
Loads the image(s), and creates their corresponding embedding. This creates one chunk for each image
:param loader: The loader whose `load_data` method is used to create
the raw data.
:param src: The data to be handled by the loader. Can be a URL for
remote sources or local content for local loaders.
"""
documents = []
embeddings = []
ids = []
data_result = loader.load_data(src)
data_records = data_result["data"]
doc_id = data_result["doc_id"]
doc_id = f"{app_id}--{doc_id}" if app_id is not None else doc_id
metadatas = []
for data in data_records:
meta_data = data["meta_data"]
# add data type to meta data to allow query using data type
meta_data["data_type"] = self.data_type.value
chunk_id = hashlib.sha256(meta_data["url"].encode()).hexdigest()
ids.append(chunk_id)
documents.append(data["content"])
embeddings.append(data["embedding"])
meta_data["doc_id"] = doc_id
metadatas.append(meta_data)
return {
"documents": documents,
"embeddings": embeddings,
"ids": ids,
"metadatas": metadatas,
"doc_id": doc_id,
}
def get_word_count(self, documents):
"""
The number of chunks and the corresponding word count for an image is fixed to 1, as 1 embedding is created for
each image
"""
return 1
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class SitemapChunker(BaseChunker):
"""Chunker for sitemap."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
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)
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
class TableChunker(BaseChunker):
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class XmlChunker(BaseChunker):
"""Chunker for XML files."""
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)
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.AddConfig import ChunkerConfig
from embedchain.config.add_config import ChunkerConfig
from embedchain.helper.json_serializable import register_deserializable
+7 -6
View File
@@ -1,13 +1,14 @@
# flake8: noqa: F401
from .AddConfig import AddConfig, ChunkerConfig
from .add_config import AddConfig, ChunkerConfig
from .apps.app_config import AppConfig
from .apps.custom_app_config import CustomAppConfig
from .apps.open_source_app_config import OpenSourceAppConfig
from .BaseConfig import BaseConfig
from .embedder.BaseEmbedderConfig import BaseEmbedderConfig
from .embedder.BaseEmbedderConfig import BaseEmbedderConfig as EmbedderConfig
from .base_config import BaseConfig
from .embedder.base import BaseEmbedderConfig
from .embedder.base import BaseEmbedderConfig as EmbedderConfig
from .llm.base_llm_config import BaseLlmConfig
from .llm.base_llm_config import BaseLlmConfig as LlmConfig
from .vectordbs.ChromaDbConfig import ChromaDbConfig
from .vectordbs.ElasticsearchDBConfig import ElasticsearchDBConfig
from .vectordb.chroma import ChromaDbConfig
from .vectordb.elasticsearch import ElasticsearchDBConfig
from .vectordb.opensearch import OpenSearchDBConfig
@@ -1,6 +1,6 @@
from typing import Callable, Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.config.base_config import BaseConfig
from embedchain.helper.json_serializable import register_deserializable
+1 -1
View File
@@ -1,7 +1,7 @@
import logging
from typing import Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.config.base_config import BaseConfig
from embedchain.helper.json_serializable import JSONSerializable
from embedchain.vectordb.base import BaseVectorDB
+3 -1
View File
@@ -2,7 +2,7 @@ import re
from string import Template
from typing import Any, Dict, Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.config.base_config import BaseConfig
from embedchain.helper.json_serializable import register_deserializable
DEFAULT_PROMPT = """
@@ -67,6 +67,7 @@ class BaseLlmConfig(BaseConfig):
deployment_name: Optional[str] = None,
system_prompt: Optional[str] = None,
where: Dict[str, Any] = None,
query_type: Optional[str] = None,
):
"""
Initializes a configuration class instance for the LLM.
@@ -112,6 +113,7 @@ class BaseLlmConfig(BaseConfig):
self.top_p = top_p
self.deployment_name = deployment_name
self.system_prompt = system_prompt
self.query_type = query_type
if self.validate_template(template):
self.template = template
@@ -1,6 +1,6 @@
from typing import Optional
from embedchain.config.BaseConfig import BaseConfig
from embedchain.config.base_config import BaseConfig
class BaseVectorDbConfig(BaseConfig):
@@ -1,6 +1,6 @@
from typing import Optional
from embedchain.config.vectordbs.BaseVectorDbConfig import BaseVectorDbConfig
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.helper.json_serializable import register_deserializable
@@ -1,7 +1,7 @@
import os
from typing import Dict, List, Optional, Union
from embedchain.config.vectordbs.BaseVectorDbConfig import BaseVectorDbConfig
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.helper.json_serializable import register_deserializable
+37
View File
@@ -0,0 +1,37 @@
from typing import Dict, Optional, Tuple
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.helper.json_serializable import register_deserializable
@register_deserializable
class OpenSearchDBConfig(BaseVectorDbConfig):
def __init__(
self,
opensearch_url: str,
http_auth: Tuple[str, str],
vector_dimension: int = 1536,
collection_name: Optional[str] = None,
dir: Optional[str] = None,
**extra_params: Dict[str, any],
):
"""
Initializes a configuration class instance for an OpenSearch client.
:param collection_name: Default name for the collection, defaults to None
:type collection_name: Optional[str], optional
:param opensearch_url: URL of the OpenSearch domain
:type opensearch_url: str, Eg, "http://localhost:9200"
:param http_auth: Tuple of username and password
:type http_auth: Tuple[str, str], Eg, ("username", "password")
:param vector_dimension: Dimension of the vector, defaults to 1536 (openai embedding model)
:type vector_dimension: int, optional
:param dir: Path to the database directory, where the database is stored, defaults to None
:type dir: Optional[str], optional
"""
self.opensearch_url = opensearch_url
self.http_auth = http_auth
self.vector_dimension = vector_dimension
self.extra_params = extra_params
super().__init__(collection_name=collection_name, dir=dir)
+11 -2
View File
@@ -1,27 +1,32 @@
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.chunkers.docs_site import DocsSiteChunker
from embedchain.chunkers.docx_file import DocxFileChunker
from embedchain.chunkers.images import ImagesChunker
from embedchain.chunkers.mdx import MdxChunker
from embedchain.chunkers.notion import NotionChunker
from embedchain.chunkers.pdf_file import PdfFileChunker
from embedchain.chunkers.qna_pair import QnaPairChunker
from embedchain.chunkers.sitemap import SitemapChunker
from embedchain.chunkers.table import TableChunker
from embedchain.chunkers.text import TextChunker
from embedchain.chunkers.web_page import WebPageChunker
from embedchain.chunkers.xml import XmlChunker
from embedchain.chunkers.youtube_video import YoutubeVideoChunker
from embedchain.config import AddConfig
from embedchain.config.AddConfig import ChunkerConfig, LoaderConfig
from embedchain.config.add_config import ChunkerConfig, LoaderConfig
from embedchain.helper.json_serializable import JSONSerializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.loaders.csv import CsvLoader
from embedchain.loaders.docs_site_loader import DocsSiteLoader
from embedchain.loaders.docx_file import DocxFileLoader
from embedchain.loaders.images import ImagesLoader
from embedchain.loaders.local_qna_pair import LocalQnaPairLoader
from embedchain.loaders.local_text import LocalTextLoader
from embedchain.loaders.mdx import MdxLoader
from embedchain.loaders.pdf_file import PdfFileLoader
from embedchain.loaders.sitemap import SitemapLoader
from embedchain.loaders.web_page import WebPageLoader
from embedchain.loaders.xml import XmlLoader
from embedchain.loaders.youtube_video import YoutubeVideoLoader
from embedchain.models.data_type import DataType
@@ -65,9 +70,11 @@ class DataFormatter(JSONSerializable):
DataType.TEXT: LocalTextLoader,
DataType.DOCX: DocxFileLoader,
DataType.SITEMAP: SitemapLoader,
DataType.XML: XmlLoader,
DataType.DOCS_SITE: DocsSiteLoader,
DataType.CSV: CsvLoader,
DataType.MDX: MdxLoader,
DataType.IMAGES: ImagesLoader,
}
lazy_loaders = {DataType.NOTION}
if data_type in loaders:
@@ -102,11 +109,13 @@ class DataFormatter(JSONSerializable):
DataType.QNA_PAIR: QnaPairChunker,
DataType.TEXT: TextChunker,
DataType.DOCX: DocxFileChunker,
DataType.WEB_PAGE: WebPageChunker,
DataType.DOCS_SITE: DocsSiteChunker,
DataType.SITEMAP: SitemapChunker,
DataType.NOTION: NotionChunker,
DataType.CSV: TableChunker,
DataType.MDX: MdxChunker,
DataType.IMAGES: ImagesChunker,
DataType.XML: XmlChunker,
}
if data_type in chunker_classes:
chunker_class: type = chunker_classes[data_type]
+49 -32
View File
@@ -21,7 +21,8 @@ from embedchain.embedder.base import BaseEmbedder
from embedchain.helper.json_serializable import JSONSerializable
from embedchain.llm.base import BaseLlm
from embedchain.loaders.base_loader import BaseLoader
from embedchain.models.data_type import DataType, DirectDataType, IndirectDataType, SpecialDataType
from embedchain.models.data_type import (DataType, DirectDataType,
IndirectDataType, SpecialDataType)
from embedchain.utils import detect_datatype
from embedchain.vectordb.base import BaseVectorDB
@@ -60,16 +61,13 @@ class EmbedChain(JSONSerializable):
"""
self.config = config
# Add subclasses
## Llm
# Llm
self.llm = llm
## Database
# Database has support for config assignment for backwards compatibility
if db is None and (not hasattr(self.config, "db") or self.config.db is None):
raise ValueError("App requires Database.")
self.db = db or self.config.db
## Embedder
# Embedder
if embedder is None:
raise ValueError("App requires Embedder.")
self.embedder = embedder
@@ -214,7 +212,7 @@ class EmbedChain(JSONSerializable):
# Send anonymous telemetry
if self.config.collect_metrics:
# it's quicker to check the variable twice than to count words when they won't be submitted.
word_count = sum([len(document.split(" ")) for document in documents])
word_count = data_formatter.chunker.get_word_count(documents)
extra_metadata = {"data_type": data_type.value, "word_count": word_count, "chunks_count": new_chunks}
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("add", extra_metadata))
@@ -255,7 +253,6 @@ class EmbedChain(JSONSerializable):
)
return self.add(source=source, data_type=data_type, metadata=metadata, config=config)
def _get_existing_doc_id(self, chunker: BaseChunker, src: Any):
"""
Get id of existing document for a given source, based on the data type
@@ -271,14 +268,16 @@ class EmbedChain(JSONSerializable):
elif chunker.data_type.value in [item.value for item in IndirectDataType]:
# These types have a indirect source reference
# As long as the reference is the same, they can be updated.
existing_embeddings_data = self.db.get(
where={
"url": src,
},
where = {"url": src}
if self.config.id is not None:
where.update({"app_id": self.config.id})
existing_embeddings = self.db.get(
where=where,
limit=1,
)
if len(existing_embeddings_data.get("metadatas", [])) > 0:
return existing_embeddings_data["metadatas"][0]["doc_id"]
if len(existing_embeddings.get("metadatas", [])) > 0:
return existing_embeddings["metadatas"][0]["doc_id"]
else:
return None
elif chunker.data_type.value in [item.value for item in SpecialDataType]:
@@ -286,14 +285,16 @@ class EmbedChain(JSONSerializable):
# Through custom logic, they can be attributed to a source and be updated.
if chunker.data_type == DataType.QNA_PAIR:
# QNA_PAIRs update the answer if the question already exists.
existing_embeddings_data = self.db.get(
where={
"question": src[0],
},
where = {"question": src[0]}
if self.config.id is not None:
where.update({"app_id": self.config.id})
existing_embeddings = self.db.get(
where=where,
limit=1,
)
if len(existing_embeddings_data.get("metadatas", [])) > 0:
return existing_embeddings_data["metadatas"][0]["doc_id"]
if len(existing_embeddings.get("metadatas", [])) > 0:
return existing_embeddings["metadatas"][0]["doc_id"]
else:
return None
else:
@@ -329,10 +330,10 @@ class EmbedChain(JSONSerializable):
:return: (List) documents (embedded text), (List) metadata, (list) ids, (int) number of chunks
"""
existing_doc_id = self._get_existing_doc_id(chunker=chunker, src=src)
app_id = self.config.id if self.config is not None else None
# Create chunks
embeddings_data = chunker.create_chunks(loader, src)
embeddings_data = chunker.create_chunks(loader, src, app_id=app_id)
# spread chunking results
documents = embeddings_data["documents"]
metadatas = embeddings_data["metadatas"]
@@ -349,12 +350,11 @@ class EmbedChain(JSONSerializable):
self.db.delete({"doc_id": existing_doc_id})
# get existing ids, and discard doc if any common id exist.
where = {"app_id": self.config.id} if self.config.id is not None else {}
# where={"url": src}
db_result = self.db.get(
ids=ids,
where=where, # optional filter
)
where = {"url": src}
if self.config.id is not None:
where["app_id"] = self.config.id
db_result = self.db.get(ids=ids, where=where) # optional filter
existing_ids = set(db_result["ids"])
if len(existing_ids):
@@ -394,10 +394,16 @@ class EmbedChain(JSONSerializable):
return list(documents), metadatas, ids, 0
# Count before, to calculate a delta in the end.
chunks_before_addition = self.count()
chunks_before_addition = self.db.count()
self.db.add(documents=documents, metadatas=metadatas, ids=ids)
count_new_chunks = self.count() - chunks_before_addition
self.db.add(
embeddings=embeddings_data.get("embeddings", None),
documents=documents,
metadatas=metadatas,
ids=ids,
skip_embedding=(chunker.data_type == DataType.IMAGES),
)
count_new_chunks = self.db.count() - chunks_before_addition
print((f"Successfully saved {src} ({chunker.data_type}). New chunks count: {count_new_chunks}"))
return list(documents), metadatas, ids, count_new_chunks
@@ -437,10 +443,21 @@ class EmbedChain(JSONSerializable):
if self.config.id is not None:
where.update({"app_id": self.config.id})
# We cannot query the database with the input query in case of an image search. This is because we need
# to bring down both the image and text to the same dimension to be able to compare them.
db_query = input_query
if hasattr(config, "query_type") and config.query_type == "Images":
# We import the clip processor here to make sure the package is not dependent on clip dependency even if the
# image dataset is not being used
from embedchain.models.clip_processor import ClipProcessor
db_query = ClipProcessor.get_text_features(query=input_query)
contents = self.db.query(
input_query=input_query,
input_query=db_query,
n_results=query_config.number_documents,
where=where,
skip_embedding=(hasattr(config, "query_type") and config.query_type == "Images"),
)
return contents
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Any, Callable, Optional
from embedchain.config.embedder.BaseEmbedderConfig import BaseEmbedderConfig
from embedchain.config.embedder.base import BaseEmbedderConfig
try:
from chromadb.api.types import Documents, Embeddings
+1 -1
View File
@@ -16,7 +16,7 @@ except RuntimeError:
from chromadb.utils import embedding_functions
class OpenAiEmbedder(BaseEmbedder):
class OpenAIEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
if self.config.model is None:
+2 -2
View File
@@ -12,10 +12,10 @@ class AntrophicLlm(BaseLlm):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return AntrophicLlm._get_athrophic_answer(prompt=prompt, config=self.config)
return AntrophicLlm._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_athrophic_answer(prompt: str, config: BaseLlmConfig) -> str:
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain.chat_models import ChatAnthropic
chat = ChatAnthropic(temperature=config.temperature, model=config.model)
+2 -2
View File
@@ -12,10 +12,10 @@ class AzureOpenAILlm(BaseLlm):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return AzureOpenAILlm._get_azure_openai_answer(prompt=prompt, config=self.config)
return AzureOpenAILlm._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_azure_openai_answer(prompt: str, config: BaseLlmConfig) -> str:
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain.chat_models import AzureChatOpenAI
if not config.deployment_name:
+3
View File
@@ -191,6 +191,9 @@ class BaseLlm(JSONSerializable):
prev_config = self.config.serialize()
self.config = config
if config is not None and config.query_type == "Images":
return contexts
if self.is_docs_site_instance:
self.config.template = DOCS_SITE_PROMPT_TEMPLATE
self.config.number_documents = 5
+43
View File
@@ -0,0 +1,43 @@
import importlib
import os
from typing import Optional
from langchain.llms import Cohere
from embedchain.config import BaseLlmConfig
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
@register_deserializable
class CohereLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
if "COHERE_API_KEY" not in os.environ:
raise ValueError("Please set the COHERE_API_KEY environment variable.")
try:
importlib.import_module("cohere")
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for Cohere are not installed."
'Please install with `pip install --upgrade "embedchain[cohere]"`'
) from None
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
if self.config.system_prompt:
raise ValueError("CohereLlm does not support `system_prompt`")
return CohereLlm._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
llm = Cohere(
cohere_api_key=os.environ["COHERE_API_KEY"],
model=config.model,
max_tokens=config.max_tokens,
temperature=config.temperature,
p=config.top_p,
)
return llm(prompt)
+2 -2
View File
@@ -14,7 +14,7 @@ class GPT4ALLLlm(BaseLlm):
self.instance = GPT4ALLLlm._get_instance(self.config.model)
def get_llm_model_answer(self, prompt):
return self._get_gpt4all_answer(prompt=prompt, config=self.config)
return self._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_instance(model):
@@ -27,7 +27,7 @@ class GPT4ALLLlm(BaseLlm):
return GPT4All(model_name=model)
def _get_gpt4all_answer(self, prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]:
def _get_answer(self, prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]:
if config.model and config.model != self.config.model:
raise RuntimeError(
"OpenSourceApp does not support switching models at runtime. Please create a new app instance."
+51
View File
@@ -0,0 +1,51 @@
import importlib
import os
from typing import Optional
from langchain.llms import HuggingFaceHub
from embedchain.config import BaseLlmConfig
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
@register_deserializable
class HuggingFaceHubLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
if "HUGGINGFACEHUB_ACCESS_TOKEN" not in os.environ:
raise ValueError("Please set the HUGGINGFACEHUB_ACCESS_TOKEN environment variable.")
try:
importlib.import_module("huggingface_hub")
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for HuggingFaceHub are not installed."
'Please install with `pip install --upgrade "embedchain[huggingface_hub]"`'
) from None
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
if self.config.system_prompt:
raise ValueError("HuggingFaceHubLlm does not support `system_prompt`")
return HuggingFaceHubLlm._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
model_kwargs = {
"temperature": config.temperature or 0.1,
"max_new_tokens": config.max_tokens,
}
if config.top_p > 0.0 and config.top_p < 1.0:
model_kwargs["top_p"] = config.top_p
else:
raise ValueError("`top_p` must be > 0.0 and < 1.0")
llm = HuggingFaceHub(
huggingfacehub_api_token=os.environ["HUGGINGFACEHUB_ACCESS_TOKEN"],
repo_id=config.model or "google/flan-t5-xxl",
model_kwargs=model_kwargs,
)
return llm(prompt)
+42
View File
@@ -0,0 +1,42 @@
import os
from typing import Optional
from langchain.chat_models import JinaChat
from langchain.schema import HumanMessage, SystemMessage
from embedchain.config import BaseLlmConfig
from embedchain.helper.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
@register_deserializable
class JinaLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
if "JINACHAT_API_KEY" not in os.environ:
raise ValueError("Please set the JINACHAT_API_KEY environment variable.")
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
response = JinaLlm._get_answer(prompt, self.config)
return response
@staticmethod
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
messages = []
if config.system_prompt:
messages.append(SystemMessage(content=config.system_prompt))
messages.append(HumanMessage(content=prompt))
kwargs = {
"temperature": config.temperature,
"max_tokens": config.max_tokens,
"model_kwargs": {},
}
if config.top_p:
kwargs["model_kwargs"]["top_p"] = config.top_p
if config.stream:
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
chat = JinaChat(**kwargs, streaming=config.stream, callbacks=[StreamingStdOutCallbackHandler()])
else:
chat = JinaChat(**kwargs)
return chat(messages).content
+26 -24
View File
@@ -1,6 +1,7 @@
from typing import Optional
import openai
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
from embedchain.config import BaseLlmConfig
from embedchain.helper.json_serializable import register_deserializable
@@ -12,31 +13,32 @@ class OpenAILlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
# NOTE: This class does not use langchain. One reason is that `top_p` is not supported.
def get_llm_model_answer(self, prompt):
messages = []
if self.config.system_prompt:
messages.append({"role": "system", "content": self.config.system_prompt})
messages.append({"role": "user", "content": prompt})
response = openai.ChatCompletion.create(
model=self.config.model or "gpt-3.5-turbo-0613",
messages=messages,
temperature=self.config.temperature,
max_tokens=self.config.max_tokens,
top_p=self.config.top_p,
stream=self.config.stream,
)
response = OpenAILlm._get_answer(prompt, self.config)
if self.config.stream:
return self._stream_llm_model_response(response)
return response
else:
return response["choices"][0]["message"]["content"]
return response.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
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
messages = []
if config.system_prompt:
messages.append(SystemMessage(content=config.system_prompt))
messages.append(HumanMessage(content=prompt))
kwargs = {
"model": config.model or "gpt-3.5-turbo",
"temperature": config.temperature,
"max_tokens": config.max_tokens,
"model_kwargs": {},
}
if config.top_p:
kwargs["model_kwargs"]["top_p"] = config.top_p
if config.stream:
from langchain.callbacks.streaming_stdout import \
StreamingStdOutCallbackHandler
chat = ChatOpenAI(**kwargs, streaming=config.stream, callbacks=[StreamingStdOutCallbackHandler()])
else:
chat = ChatOpenAI(**kwargs)
return chat(messages)
+2 -2
View File
@@ -12,10 +12,10 @@ class VertexAiLlm(BaseLlm):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return VertexAiLlm._get_athrophic_answer(prompt=prompt, config=self.config)
return VertexAiLlm._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_athrophic_answer(prompt: str, config: BaseLlmConfig) -> str:
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain.chat_models import ChatVertexAI
chat = ChatVertexAI(temperature=config.temperature, model=config.model)
+41
View File
@@ -0,0 +1,41 @@
import hashlib
import logging
import os
from embedchain.loaders.base_loader import BaseLoader
class ImagesLoader(BaseLoader):
def load_data(self, image_url):
"""
Loads images from the supplied directory/file and applies CLIP model transformation to represent these images
in vector form
:param image_url: The URL from which the images are to be loaded
"""
# load model and image preprocessing
from embedchain.models.clip_processor import ClipProcessor
model = ClipProcessor.load_model()
if os.path.isfile(image_url):
data = [ClipProcessor.get_image_features(image_url, model)]
else:
data = []
for filename in os.listdir(image_url):
filepath = os.path.join(image_url, filename)
try:
data.append(ClipProcessor.get_image_features(filepath, model))
except Exception as e:
# Log the file that was not loaded
logging.exception("Failed to load the file {}. Exception {}".format(filepath, e))
# Get the metadata like Size, Last Modified and Last Created timestamps
image_path_metadata = [
str(os.path.getsize(image_url)),
str(os.path.getmtime(image_url)),
str(os.path.getctime(image_url)),
]
doc_id = hashlib.sha256((" ".join(image_path_metadata) + image_url).encode()).hexdigest()
return {
"doc_id": doc_id,
"data": data,
}
+2 -3
View File
@@ -36,9 +36,8 @@ class SitemapLoader(BaseLoader):
for link in links:
try:
each_load_data = web_page_loader.load_data(link)
if is_readable(each_load_data[0].get("content")):
output.append(each_load_data)
if is_readable(each_load_data.get("data")[0].get("content")):
output.append(each_load_data.get("data"))
else:
logging.warning(f"Page is not readable (too many invalid characters): {link}")
except ParserRejectedMarkup as e:
+20 -15
View File
@@ -15,7 +15,25 @@ class WebPageLoader(BaseLoader):
"""Load data from a web page."""
response = requests.get(url)
data = response.content
soup = BeautifulSoup(data, "html.parser")
content = self._get_clean_content(data, url)
meta_data = {
"url": url,
}
doc_id = hashlib.sha256((content + url).encode()).hexdigest()
return {
"doc_id": doc_id,
"data": [
{
"content": content,
"meta_data": meta_data,
}
],
}
def _get_clean_content(self, html, url) -> str:
soup = BeautifulSoup(html, "html.parser")
original_size = len(str(soup.get_text()))
tags_to_exclude = [
@@ -61,17 +79,4 @@ class WebPageLoader(BaseLoader):
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,
}
content = content
doc_id = hashlib.sha256((content + url).encode()).hexdigest()
return {
"doc_id": doc_id,
"data": [
{
"content": content,
"meta_data": meta_data,
}
],
}
return content
+26
View File
@@ -0,0 +1,26 @@
import hashlib
from langchain.document_loaders import UnstructuredXMLLoader
from embedchain.helper.json_serializable import register_deserializable
from embedchain.loaders.base_loader import BaseLoader
from embedchain.utils import clean_string
@register_deserializable
class XmlLoader(BaseLoader):
def load_data(self, xml_url):
"""Load data from a XML file."""
loader = UnstructuredXMLLoader(xml_url)
data = loader.load()
content = data[0].page_content
content = clean_string(content)
meta_data = data[0].metadata
meta_data["url"] = meta_data["source"]
del meta_data["source"]
output = [{"content": content, "meta_data": meta_data}]
doc_id = hashlib.sha256((content + xml_url).encode()).hexdigest()
return {
"doc_id": doc_id,
"data": output,
}
+3 -3
View File
@@ -1,4 +1,4 @@
from .EmbeddingFunctions import EmbeddingFunctions # noqa: F401
from .Providers import Providers # noqa: F401
from .embedding_functions import EmbeddingFunctions # noqa: F401
from .providers import Providers # noqa: F401
from .vector_databases import VectorDatabases # noqa: F401
from .VectorDimensions import VectorDimensions # noqa: F401
from .vector_dimensions import VectorDimensions # noqa: F401
+44
View File
@@ -0,0 +1,44 @@
try:
from PIL import Image, UnidentifiedImageError
from sentence_transformers import SentenceTransformer
except ImportError:
raise ImportError(
"Images requires extra dependencies. Install with `pip install 'embedchain[images]'"
) from None
MODEL_NAME = "clip-ViT-B-32"
class ClipProcessor:
@staticmethod
def load_model():
"""Load data from a director of images."""
# load model and image preprocessing
model = SentenceTransformer(MODEL_NAME)
return model
@staticmethod
def get_image_features(image_url, model):
"""
Applies the CLIP model to evaluate the vector representation of the supplied image
"""
try:
# load image
image = Image.open(image_url)
except FileNotFoundError:
raise FileNotFoundError("The supplied file does not exist`")
except UnidentifiedImageError:
raise UnidentifiedImageError("The supplied file is not an image`")
image_features = model.encode(image)
meta_data = {"url": image_url}
return {"content": image_url, "embedding": image_features.tolist(), "meta_data": meta_data}
@staticmethod
def get_text_features(query):
"""
Applies the CLIP model to evaluate the vector representation of the supplied text
"""
model = ClipProcessor.load_model()
text_features = model.encode(query)
return text_features.tolist()
+4
View File
@@ -18,11 +18,13 @@ class IndirectDataType(Enum):
PDF_FILE = "pdf_file"
WEB_PAGE = "web_page"
SITEMAP = "sitemap"
XML = "xml"
DOCX = "docx"
DOCS_SITE = "docs_site"
NOTION = "notion"
CSV = "csv"
MDX = "mdx"
IMAGES = "images"
class SpecialDataType(Enum):
@@ -39,9 +41,11 @@ class DataType(Enum):
PDF_FILE = IndirectDataType.PDF_FILE.value
WEB_PAGE = IndirectDataType.WEB_PAGE.value
SITEMAP = IndirectDataType.SITEMAP.value
XML = IndirectDataType.XML.value
DOCX = IndirectDataType.DOCX.value
DOCS_SITE = IndirectDataType.DOCS_SITE.value
NOTION = IndirectDataType.NOTION.value
CSV = IndirectDataType.CSV.value
MDX = IndirectDataType.MDX.value
QNA_PAIR = SpecialDataType.QNA_PAIR.value
IMAGES = IndirectDataType.IMAGES.value
+1
View File
@@ -4,3 +4,4 @@ from enum import Enum
class VectorDatabases(Enum):
CHROMADB = "CHROMADB"
ELASTICSEARCH = "ELASTICSEARCH"
OPENSEARCH = "OPENSEARCH"
+4
View File
@@ -190,6 +190,10 @@ def detect_datatype(source: Any) -> DataType:
logging.debug(f"Source of `{formatted_source}` detected as `csv`.")
return DataType.CSV
if source.endswith(".xml"):
logging.debug(f"Source of `{formatted_source}` detected as `xml`.")
return DataType.XML
# If the source is a valid file, that's not detectable as a type, an error is raised.
# It does not fallback to text.
raise ValueError(
+1 -1
View File
@@ -1,4 +1,4 @@
from embedchain.config.vectordbs.BaseVectorDbConfig import BaseVectorDbConfig
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.embedder.base import BaseEmbedder
from embedchain.helper.json_serializable import JSONSerializable
-50
View File
@@ -1,50 +0,0 @@
from embedchain.config.vectordbs.BaseVectorDbConfig import BaseVectorDbConfig
from embedchain.embedder.base_embedder import BaseEmbedder
from embedchain.helper_classes.json_serializable import JSONSerializable
class BaseVectorDB(JSONSerializable):
"""Base class for vector database."""
def __init__(self, config: BaseVectorDbConfig):
self.client = self._get_or_create_db()
self.config: BaseVectorDbConfig = config
def _initialize(self):
"""
This method is needed because `embedder` attribute needs to be set externally before it can be initialized.
So it's can't be done in __init__ in one step.
"""
raise NotImplementedError
def _get_or_create_db(self):
"""Get or create the database."""
raise NotImplementedError
def _get_or_create_collection(self):
raise NotImplementedError
def _set_embedder(self, embedder: BaseEmbedder):
self.embedder = embedder
def get(self):
raise NotImplementedError
def add(self):
raise NotImplementedError
def query(self):
raise NotImplementedError
def count(self):
raise NotImplementedError
def delete(self):
raise NotImplementedError
def reset(self):
raise NotImplementedError
def set_collection_name(self, name: str):
raise NotImplementedError
+39 -13
View File
@@ -63,7 +63,9 @@ class ChromaDB(BaseVectorDB):
This method is needed because `embedder` attribute needs to be set externally before it can be initialized.
"""
if not self.embedder:
raise ValueError("Embedder not set. Please set an embedder with `set_embedder` before initialization.")
raise ValueError(
"Embedder not set. Please set an embedder with `_set_embedder()` function before initialization."
)
self._get_or_create_collection(self.config.collection_name)
def _get_or_create_db(self):
@@ -113,18 +115,32 @@ class ChromaDB(BaseVectorDB):
def get_advanced(self, where):
return self.collection.get(where=where, limit=1)
def add(self, documents: List[str], metadatas: List[object], ids: List[str]) -> Any:
def add(
self,
embeddings: List[List[float]],
documents: List[str],
metadatas: List[object],
ids: List[str],
skip_embedding: bool,
) -> Any:
"""
Add vectors to chroma database
:param embeddings: list of embeddings to add
:type embeddings: List[List[str]]
:param documents: Documents
:type documents: List[str]
:param metadatas: Metadatas
:type metadatas: List[object]
:param ids: ids
:type ids: List[str]
:param skip_embedding: Optional. If True, then the embeddings are assumed to be already generated.
:type skip_embedding: bool
"""
self.collection.add(documents=documents, metadatas=metadatas, ids=ids)
if skip_embedding:
self.collection.add(embeddings=embeddings, documents=documents, metadatas=metadatas, ids=ids)
else:
self.collection.add(documents=documents, metadatas=metadatas, ids=ids)
def _format_result(self, results: QueryResult) -> list[tuple[Document, float]]:
"""
@@ -144,9 +160,9 @@ class ChromaDB(BaseVectorDB):
)
]
def query(self, input_query: List[str], n_results: int, where: Dict[str, Any]) -> List[str]:
def query(self, input_query: List[str], n_results: int, where: Dict[str, any], skip_embedding: bool) -> List[str]:
"""
Query contents from vector data base based on vector similarity
Query contents from vector database based on vector similarity
:param input_query: list of query string
:type input_query: List[str]
@@ -154,24 +170,34 @@ class ChromaDB(BaseVectorDB):
:type n_results: int
:param where: to filter data
:type where: Dict[str, Any]
:param skip_embedding: Optional. If True, then the input_query is assumed to be already embedded.
:type skip_embedding: bool
:raises InvalidDimensionException: Dimensions do not match.
:return: The content of the document that matched your query.
:rtype: List[str]
"""
try:
result = self.collection.query(
query_texts=[
input_query,
],
n_results=n_results,
where=where,
)
if skip_embedding:
result = self.collection.query(
query_embeddings=[
input_query,
],
n_results=n_results,
where=where,
)
else:
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
+27 -8
View File
@@ -1,5 +1,5 @@
import logging
from typing import Dict, List, Optional, Set
from typing import Any, Dict, List, Optional, Set
try:
from elasticsearch import Elasticsearch
@@ -100,19 +100,32 @@ class ElasticsearchDB(BaseVectorDB):
ids = [doc["_id"] for doc in docs]
return {"ids": set(ids)}
def add(self, documents: List[str], metadatas: List[object], ids: List[str]):
"""add data in vector database
def add(
self,
embeddings: List[List[float]],
documents: List[str],
metadatas: List[object],
ids: List[str],
skip_embedding: bool,
) -> Any:
"""
add data in vector database
:param embeddings: list of embeddings to add
:type embeddings: List[List[str]]
:param documents: list of texts to add
:type documents: List[str]
:param metadatas: list of metadata associated with docs
:type metadatas: List[object]
:param ids: ids of docs
:type ids: List[str]
:param skip_embedding: Optional. If True, then the input_query is assumed to be already embedded.
:type skip_embedding: bool
"""
docs = []
embeddings = self.embedder.embedding_fn(documents)
if not skip_embedding:
embeddings = self.embedder.embedding_fn(documents)
for id, text, metadata, embeddings in zip(ids, documents, metadatas, embeddings):
docs.append(
{
@@ -124,7 +137,7 @@ class ElasticsearchDB(BaseVectorDB):
bulk(self.client, docs)
self.client.indices.refresh(index=self._get_index())
def query(self, input_query: List[str], n_results: int, where: Dict[str, any]) -> List[str]:
def query(self, input_query: List[str], n_results: int, where: Dict[str, any], skip_embedding: bool) -> List[str]:
"""
query contents from vector data base based on vector similarity
@@ -134,11 +147,17 @@ class ElasticsearchDB(BaseVectorDB):
:type n_results: int
:param where: Optional. to filter data
:type where: Dict[str, any]
:param skip_embedding: Optional. If True, then the input_query is assumed to be already embedded.
:type skip_embedding: bool
:return: Database contents that are the result of the query
:rtype: List[str]
"""
input_query_vector = self.embedder.embedding_fn(input_query)
query_vector = input_query_vector[0]
if skip_embedding:
query_vector = input_query
else:
input_query_vector = self.embedder.embedding_fn(input_query)
query_vector = input_query_vector[0]
query = {
"script_score": {
"query": {"bool": {"must": [{"exists": {"field": "text"}}]}},
+231
View File
@@ -0,0 +1,231 @@
import logging
from typing import Dict, List, Optional, Set
try:
from opensearchpy import OpenSearch
from opensearchpy.helpers import bulk
except ImportError:
raise ImportError(
"OpenSearch requires extra dependencies. Install with `pip install --upgrade embedchain[opensearch]`"
) from None
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import OpenSearchVectorSearch
from embedchain.config import OpenSearchDBConfig
from embedchain.helper.json_serializable import register_deserializable
from embedchain.vectordb.base import BaseVectorDB
@register_deserializable
class OpenSearchDB(BaseVectorDB):
"""
OpenSearch as vector database
"""
def __init__(self, config: OpenSearchDBConfig):
"""OpenSearch as vector database.
:param config: OpenSearch domain config
:type config: OpenSearchDBConfig
"""
if config is None:
raise ValueError("OpenSearchDBConfig is required")
self.config = config
self.client = OpenSearch(
hosts=[self.config.opensearch_url],
http_auth=self.config.http_auth,
**self.config.extra_params,
)
info = self.client.info()
logging.info(f"Connected to {info['version']['distribution']}. Version: {info['version']['number']}")
# Remove auth credentials from config after successful connection
super().__init__(config=self.config)
def _initialize(self):
logging.info(self.client.info())
index_name = self._get_index()
if self.client.indices.exists(index=index_name):
print(f"Index '{index_name}' already exists.")
return
index_body = {
"settings": {"knn": True},
"mappings": {
"properties": {
"text": {"type": "text"},
"embeddings": {
"type": "knn_vector",
"index": False,
"dimension": self.config.vector_dimension,
},
}
},
}
self.client.indices.create(index_name, body=index_body)
print(self.client.indices.get(index_name))
def _get_or_create_db(self):
"""Called during initialization"""
return self.client
def _get_or_create_collection(self, name):
"""Note: nothing to return here. Discuss later"""
def get(
self, ids: Optional[List[str]] = None, where: Optional[Dict[str, any]] = None, limit: Optional[int] = None
) -> Set[str]:
"""
Get existing doc ids present in vector database
:param ids: _list of doc ids to check for existence
:type ids: List[str]
:param where: to filter data
:type where: Dict[str, any]
:return: ids
:type: Set[str]
"""
query = {}
if ids:
query["query"] = {"bool": {"must": [{"ids": {"values": ids}}]}}
else:
query["query"] = {"bool": {"must": []}}
if "app_id" in where:
app_id = where["app_id"]
query["query"]["bool"]["must"].append({"term": {"metadata.app_id.keyword": app_id}})
# OpenSearch syntax is different from Elasticsearch
response = self.client.search(index=self._get_index(), body=query, _source=True, size=limit)
docs = response["hits"]["hits"]
ids = [doc["_id"] for doc in docs]
doc_ids = [doc["_source"]["metadata"]["doc_id"] for doc in docs]
# Result is modified for compatibility with other vector databases
# TODO: Add method in vector database to return result in a standard format
result = {"ids": ids, "metadatas": []}
for doc_id in doc_ids:
result["metadatas"].append({"doc_id": doc_id})
return result
def add(
self, embeddings: List[List[str]], documents: List[str], metadatas: List[object], ids: List[str],
skip_embedding: bool):
"""add data in vector database
:param embeddings: list of embeddings to add
:type embeddings: List[List[str]]
:param documents: list of texts to add
:type documents: List[str]
:param metadatas: list of metadata associated with docs
:type metadatas: List[object]
:param ids: ids of docs
:type ids: List[str]
:param skip_embedding: Optional. If True, then the embeddings are assumed to be already generated.
:type skip_embedding: bool
"""
docs = []
if not skip_embedding:
embeddings = self.embedder.embedding_fn(documents)
for id, text, metadata, embeddings in zip(ids, documents, metadatas, embeddings):
docs.append(
{
"_index": self._get_index(),
"_id": id,
"_source": {"text": text, "metadata": metadata, "embeddings": embeddings},
}
)
bulk(self.client, docs)
self.client.indices.refresh(index=self._get_index())
def query(self, input_query: List[str], n_results: int, where: Dict[str, any], skip_embedding: bool) -> List[str]:
"""
query contents from vector data base based on vector similarity
:param input_query: list of query string
:type input_query: List[str]
:param n_results: no of similar documents to fetch from database
:type n_results: int
:param where: Optional. to filter data
:type where: Dict[str, any]
:param skip_embedding: Optional. If True, then the input_query is assumed to be already embedded.
:type skip_embedding: bool
:return: Database contents that are the result of the query
:rtype: List[str]
"""
# TODO(rupeshbansal, deshraj): Add support for skip embeddings here if already exists
embeddings = OpenAIEmbeddings()
docsearch = OpenSearchVectorSearch(
index_name=self._get_index(),
embedding_function=embeddings,
opensearch_url=f"{self.config.opensearch_url}",
http_auth=self.config.http_auth,
use_ssl=True,
)
pre_filter = {"match_all": {}} # default
if "app_id" in where:
app_id = where["app_id"]
pre_filter = {"bool": {"must": [{"term": {"metadata.app_id.keyword": app_id}}]}}
docs = docsearch.similarity_search(
input_query,
search_type="script_scoring",
space_type="cosinesimil",
vector_field="embeddings",
text_field="text",
metadata_field="metadata",
pre_filter=pre_filter,
k=n_results,
)
contents = [doc.page_content for doc in docs]
return contents
def set_collection_name(self, name: str):
"""
Set the name of the collection. A collection is an isolated space for vectors.
:param name: Name of the collection.
:type name: str
"""
if not isinstance(name, str):
raise TypeError("Collection name must be a string")
self.config.collection_name = name
def count(self) -> int:
"""
Count number of documents/chunks embedded in the database.
:return: number of documents
:rtype: int
"""
query = {"query": {"match_all": {}}}
response = self.client.count(index=self._get_index(), body=query)
doc_count = response["count"]
return doc_count
def reset(self):
"""
Resets the database. Deletes all embeddings irreversibly.
"""
# Delete all data from the database
if self.client.indices.exists(index=self._get_index()):
# delete index in Es
self.client.indices.delete(index=self._get_index())
def delete(self, where):
"""Deletes a document from the OpenSearch index"""
if "doc_id" not in where:
raise ValueError("doc_id is required to delete a document")
query = {"query": {"bool": {"must": [{"term": {"metadata.doc_id": where["doc_id"]}}]}}}
self.client.delete_by_query(index=self._get_index(), body=query)
def _get_index(self) -> str:
"""Get the OpenSearch index for a collection
:return: OpenSearch index
:rtype: str
"""
return self.config.collection_name

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