Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4eb91683a9 | |||
| 0cb78b9067 | |||
| e226a89637 | |||
| 431f8c2c6a | |||
| 19a9141c2d | |||
| bc649b9a85 | |||
| 702067e521 | |||
| 03a84daf9d | |||
| b91d922600 | |||
| ed02aebf9a | |||
| 1a048390fd | |||
| 1741d3bef6 | |||
| 540a0a3685 | |||
| d2fd3ce434 | |||
| ea76868d65 | |||
| f31351bedb | |||
| 8863983c7b | |||
| 64a34cac32 | |||
| 352e71461d | |||
| 87d0b5c76f | |||
| d0af018b8d | |||
| 55e9a1cbd6 | |||
| 65bafb75b1 | |||
| 01fd1c2437 | |||
| 78ba4468b0 | |||
| 8581c7ecce | |||
| 9be6fe6bc3 | |||
| 8c506da21e | |||
| c02002eb4b | |||
| 57ecfca862 | |||
| 28d41e9397 | |||
| 7a1866d280 | |||
| d229b108c3 | |||
| 39640cb697 | |||
| 9ecf2e9feb | |||
| 2db07cdb1f | |||
| faa29ef285 | |||
| 16b0d5b829 | |||
| 6ae33d04b8 | |||
| 333eb8d60f | |||
| 414c69fd62 | |||
| 9951b58005 | |||
| e73ed82ae9 | |||
| 8bde34a24f | |||
| b58380e505 | |||
| 16f8de810c |
+1
-1
@@ -1 +1 @@
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_API_KEY="your-openai-api-key"
|
||||
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
[](https://twitter.com/embedchain)
|
||||
[](https://embedchain.substack.com/)
|
||||
[](https://colab.research.google.com/drive/138lMWhENGeEu7Q1-6lNbNTHGLZXBBz_B?usp=sharing)
|
||||
[](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:
|
||||
|
||||
[](https://colab.research.google.com/drive/138lMWhENGeEu7Q1-6lNbNTHGLZXBBz_B?usp=sharing)
|
||||
[](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
@@ -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
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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?")
|
||||
```
|
||||
|
||||
@@ -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!
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: '📋 Guidelines'
|
||||
url: https://github.com/embedchain/embedchain/blob/main/CONTRIBUTING.md
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: ' 🟨 Javascript'
|
||||
url: https://github.com/embedchain/embedchain/tree/main/embedchain-js
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: '🐍 Python'
|
||||
url: https://github.com/embedchain/embedchain
|
||||
---
|
||||
@@ -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.
|
||||
@@ -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?"))
|
||||
```
|
||||
@@ -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")
|
||||
```
|
||||
@@ -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")
|
||||
@@ -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')
|
||||
```
|
||||
@@ -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")
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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")
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: 'Request New Format'
|
||||
url: https://forms.gle/gB5La14tjgy4p94dA
|
||||
---
|
||||
@@ -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')
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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')
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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')
|
||||
```
|
||||
@@ -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 |
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: ' 📜 Release Notes'
|
||||
url: https://github.com/embedchain/embedchain/releases
|
||||
---
|
||||
@@ -6,6 +6,6 @@ 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+86
-15
@@ -1,11 +1,16 @@
|
||||
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.openai import OpenAiEmbedder
|
||||
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
|
||||
|
||||
|
||||
@@ -23,32 +28,98 @@ class App(EmbedChain):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig = None,
|
||||
llm_config: BaseLlmConfig = None,
|
||||
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 `CustomApp` instance. You only have a few choices to make.
|
||||
Initialize a new `App` instance.
|
||||
|
||||
: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 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: BaseLlmConfig, optional
|
||||
:param chromadb_config: Allows you to configure the vector database,
|
||||
: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)
|
||||
|
||||
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)
|
||||
# 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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,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,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
|
||||
|
||||
|
||||
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
|
||||
+47
-31
@@ -61,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
|
||||
@@ -215,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))
|
||||
@@ -256,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
|
||||
@@ -272,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]:
|
||||
@@ -287,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:
|
||||
@@ -330,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"]
|
||||
@@ -350,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):
|
||||
@@ -395,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
|
||||
|
||||
@@ -438,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,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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -27,7 +27,7 @@ class OpenAILlm(BaseLlm):
|
||||
messages.append(SystemMessage(content=config.system_prompt))
|
||||
messages.append(HumanMessage(content=prompt))
|
||||
kwargs = {
|
||||
"model": config.model or "gpt-3.5-turbo-0613",
|
||||
"model": config.model or "gpt-3.5-turbo",
|
||||
"temperature": config.temperature,
|
||||
"max_tokens": config.max_tokens,
|
||||
"model_kwargs": {},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
from .EmbeddingFunctions import EmbeddingFunctions # noqa: F401
|
||||
from .embedding_functions import EmbeddingFunctions # noqa: F401
|
||||
from .providers import Providers # noqa: F401
|
||||
from .vector_databases import VectorDatabases # noqa: F401
|
||||
from .vector_dimensions import VectorDimensions # noqa: F401
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -4,3 +4,4 @@ from enum import Enum
|
||||
class VectorDatabases(Enum):
|
||||
CHROMADB = "CHROMADB"
|
||||
ELASTICSEARCH = "ELASTICSEARCH"
|
||||
OPENSEARCH = "OPENSEARCH"
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"}}]}},
|
||||
|
||||
@@ -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
|
||||
+16
-4
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "embedchain"
|
||||
version = "0.0.61"
|
||||
version = "0.0.68"
|
||||
description = "embedchain is a framework to easily create LLM powered bots over any dataset"
|
||||
authors = ["Taranjeet Singh"]
|
||||
license = "Apache License"
|
||||
@@ -94,19 +94,25 @@ pytube = "^15.0.0"
|
||||
duckduckgo-search = "^3.8.5"
|
||||
llama-hub = { version = "^0.0.29", optional = true }
|
||||
sentence-transformers = { version = "^2.2.2", optional = true }
|
||||
torch = { version = ">=2.0.0, !=2.0.1", optional = true }
|
||||
torch = { version = "2.0.0", optional = true }
|
||||
# Torch 2.0.1 is not compatible with poetry (https://github.com/pytorch/pytorch/issues/100974)
|
||||
gpt4all = { version = "1.0.8", optional = true }
|
||||
# 1.0.9 is not working for some users (https://github.com/nomic-ai/gpt4all/issues/1394)
|
||||
opensearch-py = { version = "2.3.1", optional = true }
|
||||
elasticsearch = { version = "^8.9.0", optional = true }
|
||||
flask = { version = "^2.3.3", optional = true }
|
||||
twilio = { version = "^8.5.0", optional = true }
|
||||
fastapi-poe = { version = "0.0.16", optional = true }
|
||||
discord = { version = "^2.3.2", optional = true }
|
||||
slack-sdk = { version = "3.21.3", optional = true }
|
||||
cohere = { version = "^4.27", optional= true }
|
||||
docx2txt = "^0.8"
|
||||
|
||||
|
||||
unstructured = {extras = ["local-inference"], version = "^0.10.18"}
|
||||
pillow = { version = "10.0.1", optional = true }
|
||||
torchvision = { version = ">=0.15.1, !=0.15.2", optional = true }
|
||||
ftfy = { version = "6.1.1", optional = true }
|
||||
regex = { version = "2023.8.8", optional = true }
|
||||
huggingface_hub = { version = "^0.17.3", optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.3.0"
|
||||
@@ -117,16 +123,22 @@ pytest-mock = "^3.10.0"
|
||||
pytest-env = "^0.8.1"
|
||||
click = "^8.1.3"
|
||||
isort = "^5.12.0"
|
||||
pytest-cov = "^4.1.0"
|
||||
responses = "^0.23.3"
|
||||
|
||||
[tool.poetry.extras]
|
||||
streamlit = ["streamlit"]
|
||||
community = ["llama-hub"]
|
||||
opensource = ["sentence-transformers", "torch", "gpt4all"]
|
||||
elasticsearch = ["elasticsearch"]
|
||||
opensearch = ["opensearch-py"]
|
||||
poe = ["fastapi-poe"]
|
||||
discord = ["discord"]
|
||||
slack = ["slack-sdk", "flask"]
|
||||
whatsapp = ["twilio", "flask"]
|
||||
images = ["torch", "ftfy", "regex", "pillow", "torchvision"]
|
||||
huggingface_hub=["huggingface_hub"]
|
||||
cohere = ["cohere"]
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from embedchain import App, CustomApp, Llama2App, OpenSourceApp
|
||||
from embedchain.config import ChromaDbConfig
|
||||
from embedchain.embedder.base import BaseEmbedder
|
||||
from embedchain.llm.base import BaseLlm
|
||||
from embedchain.vectordb.base import BaseVectorDB
|
||||
from embedchain.vectordb.chroma import ChromaDB
|
||||
|
||||
|
||||
class TestApps(unittest.TestCase):
|
||||
try:
|
||||
del os.environ["OPENAI_KEY"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def test_app(self):
|
||||
app = App()
|
||||
self.assertIsInstance(app.llm, BaseLlm)
|
||||
self.assertIsInstance(app.db, BaseVectorDB)
|
||||
self.assertIsInstance(app.embedder, BaseEmbedder)
|
||||
|
||||
def test_custom_app(self):
|
||||
app = CustomApp()
|
||||
self.assertIsInstance(app.llm, BaseLlm)
|
||||
self.assertIsInstance(app.db, BaseVectorDB)
|
||||
self.assertIsInstance(app.embedder, BaseEmbedder)
|
||||
|
||||
def test_opensource_app(self):
|
||||
app = OpenSourceApp()
|
||||
self.assertIsInstance(app.llm, BaseLlm)
|
||||
self.assertIsInstance(app.db, BaseVectorDB)
|
||||
self.assertIsInstance(app.embedder, BaseEmbedder)
|
||||
|
||||
def test_llama2_app(self):
|
||||
os.environ["REPLICATE_API_TOKEN"] = "-"
|
||||
app = Llama2App()
|
||||
self.assertIsInstance(app.llm, BaseLlm)
|
||||
self.assertIsInstance(app.db, BaseVectorDB)
|
||||
self.assertIsInstance(app.embedder, BaseEmbedder)
|
||||
|
||||
|
||||
class TestConfigForAppComponents(unittest.TestCase):
|
||||
collection_name = "my-test-collection"
|
||||
|
||||
def test_constructor_config(self):
|
||||
"""
|
||||
Test that app can be configured through the app constructor.
|
||||
"""
|
||||
app = App(db_config=ChromaDbConfig(collection_name=self.collection_name))
|
||||
self.assertEqual(app.db.config.collection_name, self.collection_name)
|
||||
|
||||
def test_component_config(self):
|
||||
"""
|
||||
Test that app can also be configured by passing a configured component to the app
|
||||
"""
|
||||
database = ChromaDB(config=ChromaDbConfig(collection_name=self.collection_name))
|
||||
app = App(db=database)
|
||||
self.assertEqual(app.db.config.collection_name, self.collection_name)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user