Compare commits

...

82 Commits

Author SHA1 Message Date
Dev Khant ebbf90f4aa Version bump -> 0.1.116 (#1464) 2024-07-06 21:23:10 -07:00
Stefan Bokarev 4f119692f1 [Docs]: Add Integration for 🧊 Helicone (LLM-Observability for Developers) (#1458) 2024-07-06 12:27:57 -07:00
Dev Khant bbe56107fb Integrate Mem0 (#1462)
Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
2024-07-06 12:27:01 -07:00
Parshva Daftari bd654e7aac Fixed Docs for the Token Usage (#1461)
Co-authored-by: parshvadaftari <parshva@192.168.1.2>
2024-07-05 08:54:04 -07:00
Dev Khant 33500a7ce2 Version bump (#1460) 2024-07-04 14:42:59 -07:00
Dev Khant 4880557d51 Show details for query tokens (#1392) 2024-07-04 11:40:56 -07:00
Dev Khant ea09b5f7f0 Version bump (#1457) 2024-07-02 23:07:52 -07:00
Pranav Puranik 5258fd91ea http_client and http_async_client bugfix (#1454) 2024-07-02 16:13:33 -07:00
João Moura b305d674de Updating dependencies (#1453) 2024-07-02 16:12:52 -07:00
Pranav Puranik 7c24601d0f Adding model_kwargs for huggingface embedders. (#1450) 2024-06-29 12:37:31 -07:00
Dev Khant 50c0285cb2 Fix batch_size for vectordb (#1449) 2024-06-28 11:18:22 -07:00
Dev Khant 0a78198bb5 Add batch_size in config for VectorDB (#1448) 2024-06-27 14:45:58 -07:00
Vatsal Rathod edaeb78ccf Refactor openai embedder (#1444) 2024-06-26 10:58:12 -07:00
Dev Khant f80be2d2ea Version bump -> 0.1.113 (#1447) 2024-06-24 11:00:55 -07:00
Halan Marques 8700165b42 Fixed Azure OpenAI Deprecations and Adjusted the Tests (#1437) 2024-06-24 10:55:38 -07:00
Prashant Dixit 18fb92f1f8 Updated LanceDB Doc (#1445) 2024-06-24 10:55:20 -07:00
Nikhil Sharma 14fc6bbadd change: replaced deprecated gpt-4-perview with gpt-4o (#1443) 2024-06-24 10:27:10 -07:00
Dev Khant 5070a1d83e Change HF embedding library (#1440) 2024-06-22 01:38:29 -07:00
Dev Khant 8a9088ea9d Version bump (#1438) 2024-06-21 09:11:24 -07:00
Prashant Dixit 48b24f6f12 Lancedb Integration (#1411) 2024-06-21 08:59:22 -07:00
Dev Khant f6ddd5ffc5 Add HF endpoint in embedder (#1436) 2024-06-21 08:57:21 -07:00
Dev Khant b43a116b3c Add vector dimension to Ollama embedder (#1435) 2024-06-21 08:56:46 -07:00
Dev Khant 50512a5f03 Doc fix for embedders (#1433) 2024-06-19 10:08:31 -07:00
Dev Khant e3e107b31d Raise import error if Ollama and Google not found (#1432) 2024-06-18 21:46:48 -07:00
Dev Khant 21a04541ea poetry fix (#1430) 2024-06-18 10:45:37 -07:00
Dev Khant cdd5d8ac76 Version bump (#1426) 2024-06-18 09:13:52 -07:00
Dev Khant 11094f504e Fix Ollama test (#1428) 2024-06-18 09:10:43 -07:00
mogith-pn 5acaae5f56 Clarifai : Added Clarifai as LLM and embedding model provider. (#1311)
Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
2024-06-17 08:48:18 -07:00
Pranav Puranik 4547d870af azure openai features and bugs solve - openai_version, docs (#1425) 2024-06-17 08:47:27 -07:00
Aditya Veer Parmar dc0d8e0932 Allow ollama llm to take custom callback for handling streaming (#1376) 2024-06-17 08:44:52 -07:00
patcher9 c558eae9ce [Docs]: Fix the Title and Description for OpenLIT Integration (#1424) 2024-06-14 00:09:31 -07:00
patcher9 abb9af66a6 [Docs]: Add Integration for OpenLIT (OpenTelemetry-native LLM Application O11y) (#1377) 2024-06-13 23:06:04 -07:00
Ananto Joyoadikusumo 4800e0344c Added language detection for non-english youtube videos (#1362) 2024-06-13 23:02:37 -07:00
Dev Khant 439b425c61 Version bump (#1423) 2024-06-13 22:28:35 -07:00
Dev Khant 2855f1635b Add support for loading api_key from config or env variable (#1421) 2024-06-13 11:19:54 -07:00
Dev Khant 08b67b4a78 Support for Audio Files (#1416) 2024-06-12 10:25:58 -07:00
Dev Khant 1bddd46ed2 Verion bump, chromadb_version change and doc update (#1407) 2024-06-12 08:46:00 -07:00
Pranav Puranik 6ecdadfd97 Add model_kwargs to OpenAI call (#1402) 2024-06-11 11:20:04 -07:00
Dimitra Gerontaki 4119040005 Add documentation for text_file data type (#1410) 2024-06-10 21:34:28 -07:00
Taranjeet Singh 873eef6ef8 Remove: EC deployment docs, and js links (#1409) 2024-06-11 02:34:15 +05:30
Taranjeet Singh 445fed4d3f Remove embedchain js (#1408) 2024-06-11 01:54:56 +05:30
Dev Khant 52fd3e0dd4 Update contributing doc (#1404) 2024-06-10 10:14:52 -07:00
Saurabh Misra 8fd0e1f3b0 ⚡️ Speed up read_env_file() in embedchain/utils/cli.py (#1260) 2024-06-09 09:11:15 -07:00
golemus 11fc4a8451 Update llms card to properly use local ollama (#1395) 2024-06-09 09:09:49 -07:00
shuo e22293294e Delete embedchain/embedder/.ollama.py.swp (#1398) 2024-06-09 09:02:38 -07:00
Dev Khant 73e53aaff1 Download Ollama model if not present (#1397) 2024-06-08 23:43:03 -07:00
Deshraj Yadav 6fa946557f Update package version to 0.1.108 (#1396) 2024-06-08 10:34:15 -07:00
Youbin Choi fb0852f585 [Bug Fix] Fix issue of loading other languages in csv file (#1225) 2024-06-08 10:09:29 -07:00
Dev Khant 4070fc1bf0 Fix ollama embeddings for remote machine (#1394) 2024-06-08 10:08:15 -07:00
Dev Khant 00c1fa1ec7 Fix OpenAI Assistant (#1393) 2024-06-08 10:07:52 -07:00
Dev Khant 04e77ef34e version bump (#1389) 2024-06-07 10:30:22 -07:00
Dev Khant 827d63d115 Fix skipped tests (#1385) 2024-06-07 10:26:54 -07:00
Anu e0d0f6e94c Change list[str] -> str for vectordbs (#1388) 2024-06-07 09:15:40 -07:00
Dev Khant fd07513004 Fix online feat and add docs (#1387) 2024-06-06 23:33:16 -07:00
Dev Khant b0e436d9c4 Poetry fixes (#1382) 2024-06-06 10:41:46 -07:00
Dev Khant a4bfd9cfc6 Version bump (#1386) 2024-06-06 10:40:38 -07:00
Dev Khant 8ca01918e5 Ollama embeddings tested and Docs ready (#1384) 2024-06-06 10:29:01 -07:00
Deshraj Yadav a5b2381458 Update version to 0.1.105 (#1383) 2024-06-05 10:53:34 -07:00
Anu 26c771503b Doc string fix for embedchain.py (#1381) 2024-06-05 10:44:09 -07:00
Saurabh Misra 622ed4a7c9 Speed up _auto_encoder() by 15% in embedchain/helpers/json_serializable.py (#1265) 2024-06-05 10:40:46 -07:00
Saurabh Misra 940f0128d5 Speed up docs site loader (#1266) 2024-06-05 10:39:30 -07:00
Saurabh Misra 1354747ca8 ⚡️ Speed up get_word_count() by 6% in embedchain/chunkers/base_chunker.py (#1268) 2024-06-05 10:36:00 -07:00
Deshraj Yadav 9544c69c55 [Improvements] Upgrade langchain-openai package and other improvements (#1372) 2024-05-21 23:42:50 -07:00
LeonieFreisinger 9ba445e623 Fix cohere embedder (#1353) 2024-05-21 22:55:10 -07:00
Abdur Rahman Nawaz ebc5e25f98 Add support for http clients in config (#1355) 2024-05-06 10:32:46 -07:00
Esparon1 78301ee63d Add feature to extract timestamps from youtube videos (#1345) 2024-05-06 10:31:04 -07:00
Niv Hertz 797dea1dca Support supplying custom headers to OpenAI requests (#1356) 2024-05-06 10:26:12 -07:00
Deshraj Yadav a0ff764f0a [Misc] Update package version for chroma and pypdf (#1352) 2024-05-01 22:24:49 -07:00
Colin O'Brien a795798156 Add Ollama as a supported embedding provider (#1344) 2024-05-01 22:08:47 -07:00
Jesús Ferretti 1a66f961f4 Docs: fix typo (#1350) 2024-05-01 22:06:22 -07:00
Deshraj Yadav 6fb2048af0 [Bug fix] Remove duplicate constants (#1342) 2024-04-19 09:50:37 -07:00
Deshraj Yadav ba9f186fc5 [Improvement] Make embedchain home dir configurable (#1341) 2024-04-18 11:20:01 -07:00
Dev Khant 6c32d287b5 Support for Excel files (#1319) 2024-04-15 22:03:43 -07:00
Deshraj Yadav 536f85b78a [Improvements] Improve logging and fix insertion in data_sources table (#1337) 2024-04-11 15:00:04 -07:00
neilbhutada f8619870ad Update llms.mdx (#1336) 2024-04-10 16:03:03 -07:00
Deshraj Yadav d00a2085d5 [Bug Fix] Make claude-3-opus model work (#1331) 2024-03-28 00:56:14 -07:00
Deshraj Yadav 85ec61335a Update package version to 0.1.98 (#1327) 2024-03-20 19:29:51 -07:00
Flyfoxs 9b48a12c27 [Bug fix] Avoid saving the duplicated docs (#1326) 2024-03-20 09:58:11 -07:00
Deshraj Yadav c181ccbe42 Update requirements.txt (#1325) 2024-03-19 21:27:21 -07:00
Deshraj Yadav 8520033d44 [Bug fix] Fix issues related to logging configuration (#1318) 2024-03-14 00:45:37 -07:00
Deshraj Yadav ebdce87fde [Version] Update version to 0.1.96 (#1317) 2024-03-14 00:02:51 -07:00
Abhishek Sharma f2122ed696 [Fix] Added missing provider for 'vllm' (#1316) 2024-03-14 00:01:30 -07:00
168 changed files with 7790 additions and 24943 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.220'
rev: 'v0.0.252'
hooks:
- id: ruff
name: ruff
+4
View File
@@ -67,6 +67,10 @@ We use `pytest` to test our code. You can run the tests by running the following
poetry run pytest
```
Several packages have been removed from Poetry to make the package lighter. Therefore, it is recommended to run `make install_all` to install the remaining packages and ensure all tests pass.
Make sure that all tests pass before submitting a pull request.
## 🚀 Release Process
+1 -1
View File
@@ -11,7 +11,7 @@ install:
install_all:
poetry install --all-extras
poetry run pip install pinecone-text pinecone-client
poetry run pip install pinecone-text pinecone-client langchain-anthropic "unstructured[local-inference, all-docs]" ollama langchain_together==0.1.3 langchain_cohere==0.1.5 deepgram-sdk==3.2.7 langchain-huggingface psutil
install_es:
poetry install --extras elasticsearch
+12
View File
@@ -0,0 +1,12 @@
llm:
provider: clarifai
config:
model: "https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct"
model_kwargs:
temperature: 0.5
max_tokens: 1000
embedder:
provider: clarifai
config:
model: "https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15"
+3 -2
View File
@@ -8,6 +8,7 @@ llm:
base_url: http://localhost:11434
embedder:
provider: huggingface
provider: ollama
config:
model: 'BAAI/bge-small-en-v1.5'
model: 'mxbai-embed-large:latest'
base_url: http://localhost:11434
+23 -4
View File
@@ -26,6 +26,11 @@ llm:
top_p: 1
stream: false
api_key: sk-xxx
model_kwargs:
response_format:
type: json_object
api_version: 2024-02-01
http_client_proxies: http://testproxy.mem0.net:8000
prompt: |
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
@@ -83,7 +88,10 @@ cache:
"stream": false,
"prompt": "Use the following pieces of context to answer the query at the end.\nIf you don't know the answer, just say that you don't know, don't try to make up an answer.\n$context\n\nQuery: $query\n\nHelpful Answer:",
"system_prompt": "Act as William Shakespeare. Answer the following questions in the style of William Shakespeare.",
"api_key": "sk-xxx"
"api_key": "sk-xxx",
"model_kwargs": {"response_format": {"type": "json_object"}},
"api_version": "2024-02-01",
"http_client_proxies": "http://testproxy.mem0.net:8000",
}
},
"vectordb": {
@@ -143,7 +151,9 @@ config = {
'system_prompt': (
"Act as William Shakespeare. Answer the following questions in the style of William Shakespeare."
),
'api_key': 'sk-xxx'
'api_key': 'sk-xxx',
"model_kwargs": {"response_format": {"type": "json_object"}},
"http_client_proxies": "http://testproxy.mem0.net:8000",
}
},
'vectordb': {
@@ -198,28 +208,34 @@ Alright, let's dive into what each key means in the yaml config above:
- `max_tokens` (Integer): Controls how many tokens are used in the response.
- `top_p` (Float): Controls the diversity of word selection. A higher value (closer to 1) makes word selection more diverse.
- `stream` (Boolean): Controls if the response is streamed back to the user (set to false).
- `online` (Boolean): Controls whether to use internet to get more context for answering query (set to false).
- `token_usage` (Boolean): Controls whether to use token usage for the querying models (set to false).
- `prompt` (String): A prompt for the model to follow when generating responses, requires `$context` and `$query` variables.
- `system_prompt` (String): A system prompt for the model to follow when generating responses, in this case, it's set to the style of William Shakespeare.
- `stream` (Boolean): Controls if the response is streamed back to the user (set to false).
- `number_documents` (Integer): Number of documents to pull from the vectordb as context, defaults to 1
- `api_key` (String): The API key for the language model.
- `model_kwargs` (Dict): Keyword arguments to pass to the language model. Used for `aws_bedrock` provider, since it requires different arguments for each model.
- `http_client_proxies` (Dict | String): The proxy server settings used to create `self.http_client` using `httpx.Client(proxies=http_client_proxies)`
- `http_async_client_proxies` (Dict | String): The proxy server settings for async calls used to create `self.http_async_client` using `httpx.AsyncClient(proxies=http_async_client_proxies)`
3. `vectordb` Section:
- `provider` (String): The provider for the vector database, set to 'chroma'. You can find the full list of vector database providers in [our docs](/components/vector-databases).
- `config`:
- `collection_name` (String): The initial collection name for the vectordb, set to 'full-stack-app'.
- `dir` (String): The directory for the local database, set to 'db'.
- `allow_reset` (Boolean): Indicates whether resetting the vectordb is allowed, set to true.
- `batch_size` (Integer): The batch size for docs insertion in vectordb, defaults to `100`
<Note>We recommend you to checkout vectordb specific config [here](https://docs.embedchain.ai/components/vector-databases)</Note>
4. `embedder` Section:
- `provider` (String): The provider for the embedder, set to 'openai'. You can find the full list of embedding model providers in [our docs](/components/embedding-models).
- `config`:
- `model` (String): The specific model used for text embedding, 'text-embedding-ada-002'.
- `vector_dimension` (Integer): The vector dimension of the embedding model. [Defaults](https://github.com/embedchain/embedchain/blob/e572b5a3dc1b66f1e9b3357d11a88c63b5ce06e3/embedchain/models/vector_dimensions.py)
- `vector_dimension` (Integer): The vector dimension of the embedding model. [Defaults](https://github.com/embedchain/embedchain/blob/main/embedchain/models/vector_dimensions.py)
- `api_key` (String): The API key for the embedding model.
- `endpoint` (String): The endpoint for the HuggingFace embedding model.
- `deployment_name` (String): The deployment name for the embedding model.
- `title` (String): The title for the embedding model for Google Embedder.
- `task_type` (String): The task type for the embedding model for Google Embedder.
- `model_kwargs` (Dict): Used to pass extra arguments to embedders.
5. `chunker` Section:
- `chunk_size` (Integer): The size of each chunk of text that is sent to the language model.
- `chunk_overlap` (Integer): The amount of overlap between each chunk of text.
@@ -233,6 +249,9 @@ Alright, let's dive into what each key means in the yaml config above:
- `config` (Optional): The config for initializing the cache. If not provided, sensible default values are used as mentioned below.
- `similarity_threshold` (Float): The threshold for similarity evaluation. Defaults to `0.8`.
- `auto_flush` (Integer): The number of queries after which the cache is flushed. Defaults to `20`.
7. `memory` Section: (Optional)
- `api_key` (String): The API key of mem0.
- `top_k` (Integer): The number of top-k results to return. Defaults to `10`.
<Note>
If you provide a cache section, the app will automatically configure and use a cache to store the results of the language model. This is useful if you want to speed up the response time and save inference cost of your app.
</Note>
+25
View File
@@ -144,3 +144,28 @@ app.add("https://www.forbes.com/profile/elon-musk")
query_config = BaseLlmConfig(number_documents=5)
app.chat("What is the net worth of Elon Musk?", config=query_config)
```
### With Mem0 to store chat history
Mem0 is a cutting-edge long-term memory for LLMs to enable personalization for the GenAI stack. It enables LLMs to remember past interactions and provide more personalized responses.
Follow these steps to use Mem0 to enable memory for personalization in your apps:
- Install the [`mem0`](https://docs.mem0.ai/) package using `pip install memzero`.
- Get the api_key from [Mem0 Platform](https://app.mem0.ai/).
- Provide api_key in config under `memory`, refer [Configurations](docs/api-reference/advanced/configuration.mdx).
```python with mem0
from embedchain import App
config = {
"memory": {
"api_key": "m0-xxx",
"top_k": 5
}
}
app = App.from_config(config=config)
app.add("https://www.forbes.com/profile/elon-musk")
app.chat("What is the net worth of Elon Musk?")
```
+25
View File
@@ -0,0 +1,25 @@
---
title: "🎤 Audio"
---
To use an audio as data source, just add `data_type` as `audio` and pass in the path of the audio (local or hosted).
We use [Deepgram](https://developers.deepgram.com/docs/introduction) to transcribe the audiot to text, and then use the generated text as the data source.
You would require an Deepgram API key which is available [here](https://console.deepgram.com/signup?jump=keys) to use this feature.
### Without customization
```python
import os
from embedchain import App
os.environ["DEEPGRAM_API_KEY"] = "153xxx"
app = App()
app.add("introduction.wav", data_type="audio")
response = app.query("What is my name and how old am I?")
print(response)
# Answer: Your name is Dave and you are 21 years old.
```
@@ -0,0 +1,18 @@
---
title: '📄 Excel file'
---
### Excel file
To add any xlsx/xls file, use the data_type as `excel_file`. `excel_file` allows remote urls and conventional file paths. Eg:
```python
from embedchain import App
app = App()
app.add('https://example.com/content/intro.xlsx', data_type="excel_file")
# Or add file using the local file path on your system
# app.add('content/intro.xls', data_type="excel_file")
app.query("Give brief information about data.")
```
@@ -9,6 +9,7 @@ Embedchain comes with built-in support for various data sources. We handle the c
<Card title="CSV file" href="/components/data-sources/csv"></Card>
<Card title="JSON file" href="/components/data-sources/json"></Card>
<Card title="Text" href="/components/data-sources/text"></Card>
<Card title="Text File" href="/components/data-sources/text-file"></Card>
<Card title="Directory" href="/components/data-sources/directory"></Card>
<Card title="Web page" href="/components/data-sources/web-page"></Card>
<Card title="Youtube Channel" href="/components/data-sources/youtube-channel"></Card>
@@ -33,6 +34,7 @@ Embedchain comes with built-in support for various data sources. We handle the c
<Card title="Beehiiv" href="/components/data-sources/beehiiv"></Card>
<Card title="Dropbox" href="/components/data-sources/dropbox"></Card>
<Card title="Image" href="/components/data-sources/image"></Card>
<Card title="Audio" href="/components/data-sources/audio"></Card>
<Card title="Custom" href="/components/data-sources/custom"></Card>
</CardGroup>
+2 -2
View File
@@ -1,5 +1,5 @@
---
title: '❓💬 Queston and answer pair'
title: '❓💬 Question and answer 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:
@@ -10,4 +10,4 @@ from embedchain import App
app = App()
app.add(("Question", "Answer"), data_type="qna_pair")
```
```
@@ -0,0 +1,14 @@
---
title: '📄 Text file'
---
To add a .txt file, specify the data_type as `text_file`. The URL provided in the first parameter of the `add` function, should be a local path. Eg:
```python
from embedchain import App
app = App()
app.add('path/to/file.txt', data_type="text_file")
app.query("Summarize the information of the text file")
```
+78
View File
@@ -15,6 +15,8 @@ Embedchain supports several embedding models from the following providers:
<Card title="Vertex AI" href="#vertex-ai"></Card>
<Card title="NVIDIA AI" href="#nvidia-ai"></Card>
<Card title="Cohere" href="#cohere"></Card>
<Card title="Ollama" href="#ollama"></Card>
<Card title="Clarifai" href="#clarifai"></Card>
</CardGroup>
## OpenAI
@@ -190,6 +192,8 @@ embedder:
provider: huggingface
config:
model: 'sentence-transformers/all-mpnet-base-v2'
model_kwargs:
trust_remote_code: True # Only use if you trust your embedder
```
</CodeGroup>
@@ -357,4 +361,78 @@ embedder:
vector_dimension: 768
```
</CodeGroup>
## Ollama
Ollama enables the use of embedding models, allowing you to generate high-quality embeddings directly on your local machine. Make sure to install [Ollama](https://ollama.com/download) and keep it running before using the embedding model.
You can find the list of models at [Ollama Embedding Models](https://ollama.com/blog/embedding-models).
Below is an example of how to use embedding model Ollama:
<CodeGroup>
```python main.py
import os
from embedchain import App
# load embedding model configuration from config.yaml file
app = App.from_config(config_path="config.yaml")
```
```yaml config.yaml
embedder:
provider: ollama
config:
model: 'all-minilm:latest'
```
</CodeGroup>
## Clarifai
Install related dependencies using the following command:
```bash
pip install --upgrade 'embedchain[clarifai]'
```
set the `CLARIFAI_PAT` as environment variable which you can find in the [security page](https://clarifai.com/settings/security). Optionally you can also pass the PAT key as parameters in LLM/Embedder class.
Now you are all set with exploring Embedchain.
<CodeGroup>
```python main.py
import os
from embedchain import App
os.environ["CLARIFAI_PAT"] = "XXX"
# load llm and embedder configuration from config.yaml file
app = App.from_config(config_path="config.yaml")
#Now let's add some data.
app.add("https://www.forbes.com/profile/elon-musk")
#Query the app
response = app.query("what college degrees does elon musk have?")
```
Head to [Clarifai Platform](https://clarifai.com/explore/models?page=1&perPage=24&filterData=%5B%7B%22field%22%3A%22output_fields%22%2C%22value%22%3A%5B%22embeddings%22%5D%7D%5D) to explore all the State of the Art embedding models available to use.
For passing LLM model inference parameters use `model_kwargs` argument in the config file. Also you can use `api_key` argument to pass `CLARIFAI_PAT` in the config.
```yaml config.yaml
llm:
provider: clarifai
config:
model: "https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct"
model_kwargs:
temperature: 0.5
max_tokens: 1000
embedder:
provider: clarifai
config:
model: "https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15"
```
</CodeGroup>
+115 -2
View File
@@ -15,6 +15,7 @@ Embedchain comes with built-in support for various popular large language models
<Card title="Together" href="#together"></Card>
<Card title="Ollama" href="#ollama"></Card>
<Card title="vLLM" href="#vllm"></Card>
<Card title="Clarifai" href="#clarifai"></Card>
<Card title="GPT4All" href="#gpt4all"></Card>
<Card title="JinaChat" href="#jinachat"></Card>
<Card title="Hugging Face" href="#hugging-face"></Card>
@@ -193,8 +194,8 @@ import os
from embedchain import App
os.environ["OPENAI_API_TYPE"] = "azure"
os.environ["OPENAI_API_BASE"] = "https://xxx.openai.azure.com/"
os.environ["OPENAI_API_KEY"] = "xxx"
os.environ["AZURE_OPENAI_ENDPOINT"] = "https://xxx.openai.azure.com/"
os.environ["AZURE_OPENAI_KEY"] = "xxx"
os.environ["OPENAI_API_VERSION"] = "xxx"
app = App.from_config(config_path="config.yaml")
@@ -330,6 +331,7 @@ Setup Ollama using https://github.com/jmorganca/ollama
```python main.py
import os
os.environ["OLLAMA_HOST"] = "http://127.0.0.1:11434"
from embedchain import App
# load llm configuration from config.yaml file
@@ -344,6 +346,13 @@ llm:
temperature: 0.5
top_p: 1
stream: true
base_url: 'http://localhost:11434'
embedder:
provider: ollama
config:
model: znbang/bge:small-en-v1.5-q8_0
base_url: http://localhost:11434
```
</CodeGroup>
@@ -377,6 +386,54 @@ llm:
</CodeGroup>
## Clarifai
Install related dependencies using the following command:
```bash
pip install --upgrade 'embedchain[clarifai]'
```
set the `CLARIFAI_PAT` as environment variable which you can find in the [security page](https://clarifai.com/settings/security). Optionally you can also pass the PAT key as parameters in LLM/Embedder class.
Now you are all set with exploring Embedchain.
<CodeGroup>
```python main.py
import os
from embedchain import App
os.environ["CLARIFAI_PAT"] = "XXX"
# load llm configuration from config.yaml file
app = App.from_config(config_path="config.yaml")
#Now let's add some data.
app.add("https://www.forbes.com/profile/elon-musk")
#Query the app
response = app.query("what college degrees does elon musk have?")
```
Head to [Clarifai Platform](https://clarifai.com/explore/models?page=1&perPage=24&filterData=%5B%7B%22field%22%3A%22use_cases%22%2C%22value%22%3A%5B%22llm%22%5D%7D%5D) to browse various State-of-the-Art LLM models for your use case.
For passing model inference parameters use `model_kwargs` argument in the config file. Also you can use `api_key` argument to pass `CLARIFAI_PAT` in the config.
```yaml config.yaml
llm:
provider: clarifai
config:
model: "https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct"
model_kwargs:
temperature: 0.5
max_tokens: 1000
embedder:
provider: clarifai
config:
model: "https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15"
```
</CodeGroup>
## GPT4ALL
Install related dependencies using the following command:
@@ -783,6 +840,62 @@ answer = app.query("What is the net worth of Elon Musk today?")
```
</CodeGroup>
## Token Usage
You can get the cost of the query by setting `token_usage` to `True` in the config file. This will return the token details: `prompt_tokens`, `completion_tokens`, `total_tokens`, `total_cost`, `cost_currency`.
The list of paid LLMs that support token usage are:
- OpenAI
- Vertex AI
- Anthropic
- Cohere
- Together
- Groq
- Mistral AI
- NVIDIA AI
Here is an example of how to use token usage:
<CodeGroup>
```python main.py
os.environ["OPENAI_API_KEY"] = "xxx"
app = App.from_config(config_path="config.yaml")
app.add("https://www.forbes.com/profile/elon-musk")
response = app.query("what is the net worth of Elon Musk?")
# {'answer': 'Elon Musk's net worth is $209.9 billion as of 6/9/24.',
# 'usage': {'prompt_tokens': 1228,
# 'completion_tokens': 21,
# 'total_tokens': 1249,
# 'total_cost': 0.001884,
# 'cost_currency': 'USD'}
# }
response = app.chat("Which companies did Elon Musk found?")
# {'answer': 'Elon Musk founded six companies, including Tesla, which is an electric car maker, SpaceX, a rocket producer, and the Boring Company, a tunneling startup.',
# 'usage': {'prompt_tokens': 1616,
# 'completion_tokens': 34,
# 'total_tokens': 1650,
# 'total_cost': 0.002492,
# 'cost_currency': 'USD'}
# }
```
```yaml config.yaml
llm:
provider: openai
config:
model: gpt-3.5-turbo
temperature: 0.5
max_tokens: 1000
token_usage: true
```
</CodeGroup>
If a model is missing and you'd like to add it to `model_prices_and_context_window.json`, please feel free to open a PR.
<br/ >
<Snippet file="missing-llm-tip.mdx" />
@@ -0,0 +1,100 @@
---
title: LanceDB
---
## Install Embedchain with LanceDB
Install Embedchain, LanceDB and related dependencies using the following command:
```bash
pip install "embedchain[lancedb]"
```
LanceDB is a developer-friendly, open source database for AI. From hyper scalable vector search and advanced retrieval for RAG, to streaming training data and interactive exploration of large scale AI datasets.
In order to use LanceDB as vector database, not need to set any key for local use.
### With OPENAI
<CodeGroup>
```python main.py
import os
from embedchain import App
# set OPENAI_API_KEY as env variable
os.environ["OPENAI_API_KEY"] = "sk-xxx"
# create Embedchain App and set config
app = App.from_config(config={
"vectordb": {
"provider": "lancedb",
"config": {
"collection_name": "lancedb-index"
}
}
}
)
# add data source and start query in
app.add("https://www.forbes.com/profile/elon-musk")
# query continuously
while(True):
question = input("Enter question: ")
if question in ['q', 'exit', 'quit']:
break
answer = app.query(question)
print(answer)
```
</CodeGroup>
### With Local LLM
<CodeGroup>
```python main.py
from embedchain import Pipeline as App
# config for Embedchain App
config = {
'llm': {
'provider': 'huggingface',
'config': {
'model': 'mistralai/Mistral-7B-v0.1',
'temperature': 0.1,
'max_tokens': 250,
'top_p': 0.1,
'stream': True
}
},
'embedder': {
'provider': 'huggingface',
'config': {
'model': 'sentence-transformers/all-mpnet-base-v2'
}
},
'vectordb': {
'provider': 'lancedb',
'config': {
'collection_name': 'lancedb-index'
}
}
}
app = App.from_config(config=config)
# add data source and start query in
app.add("https://www.tesla.com/ns_videos/2022-tesla-impact-report.pdf")
# query continuously
while(True):
question = input("Enter question: ")
if question in ['q', 'exit', 'quit']:
break
answer = app.query(question)
print(answer)
```
</CodeGroup>
<Snippet file="missing-vector-db-tip.mdx" />
-4
View File
@@ -1,4 +0,0 @@
---
title: ' 🟨 Javascript'
url: https://github.com/embedchain/embedchain/tree/main/embedchain-js
---
-17
View File
@@ -1,17 +0,0 @@
---
title: 'Embedchain.ai'
description: 'Deploy your RAG application to embedchain.ai platform'
---
## Deploy on Embedchain Platform
Embedchain enables developers to deploy their LLM-powered apps in production using the Embedchain platform. The platform offers free access to context on your data through its REST API. Once the pipeline is deployed, you can update your data sources anytime after deployment.
Deployment to Embedchain Platform is currently available on an invitation-only basis. To request access, please submit your information via the provided [Google Form](https://forms.gle/vigN11h7b4Ywat668). We will review your request and respond promptly.
## Seeking help?
If you run into issues with deployment, please feel free to reach out to us via any of the following methods:
<Snippet file="get-help.mdx" />
-1
View File
@@ -13,7 +13,6 @@ After successfully setting up and testing your RAG app locally, the next step is
<Card title="Streamlit.io" href="/deployment/streamlit_io"></Card>
<Card title="Gradio.app" href="/deployment/gradio_app"></Card>
<Card title="Huggingface.co" href="/deployment/huggingface_spaces"></Card>
<Card title="Embedchain.ai" href="/deployment/embedchain_ai"></Card>
</CardGroup>
## Seeking help?
Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

+52
View File
@@ -0,0 +1,52 @@
---
title: "🧊 Helicone"
description: "Implement Helicone, the open-source LLM observability platform, with Embedchain. Monitor, debug, and optimize your AI applications effortlessly."
"twitter:title": "Helicone LLM Observability for Embedchain"
---
Get started with [Helicone](https://www.helicone.ai/), the open-source LLM observability platform for developers to monitor, debug, and optimize their applications.
To use Helicone, you need to do the following steps.
## Integration Steps
<Steps>
<Step title="Create an account + Generate an API Key">
Log into [Helicone](https://www.helicone.ai) or create an account. Once you have an account, you
can generate an [API key](https://helicone.ai/developer).
<Note>
Make sure to generate a [write only API key](helicone-headers/helicone-auth).
</Note>
</Step>
<Step title="Set base_url in the your code">
You can configure your base_url and OpenAI API key in your codebase
<CodeGroup>
```python main.py
import os
from embedchain import App
# Modify the base path and add a Helicone URL
os.environ["OPENAI_API_BASE"] = "https://oai.helicone.ai/{YOUR_HELICONE_API_KEY}/v1"
# Add your OpenAI API Key
os.environ["OPENAI_API_KEY"] = "{YOUR_OPENAI_API_KEY}"
app = App()
# Add data to your app
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
# Query your app
print(app.query("How many companies did Elon found? Which companies?"))
```
</CodeGroup>
</Step>
<Step title="Now you can see all passing requests through Embedchain in Helicone">
<img src="/images/helicone-embedchain.png" alt="Embedchain requests" />
</Step>
</Steps>
Check out [Helicone](https://www.helicone.ai) to see more use cases!
+50
View File
@@ -0,0 +1,50 @@
---
title: '🔭 OpenLIT'
description: 'OpenTelemetry-native Observability and Evals for LLMs & GPUs'
---
Embedchain now supports integration with [OpenLIT](https://github.com/openlit/openlit).
## Getting Started
### 1. Set environment variables
```bash
# Setting environment variable for OpenTelemetry destination and authetication.
export OTEL_EXPORTER_OTLP_ENDPOINT = "YOUR_OTEL_ENDPOINT"
export OTEL_EXPORTER_OTLP_HEADERS = "YOUR_OTEL_ENDPOINT_AUTH"
```
### 2. Install the OpenLIT SDK
Open your terminal and run:
```shell
pip install openlit
```
### 3. Setup Your Application for Monitoring
Now create an app using Embedchain and initialize OpenTelemetry monitoring
```python
from embedchain import App
import OpenLIT
# Initialize OpenLIT Auto Instrumentation for monitoring.
openlit.init()
# Initialize EmbedChain application.
app = App()
# Add data to your app
app.add("https://en.wikipedia.org/wiki/Elon_Musk")
# Query your app
app.query("How many companies did Elon found?")
```
### 4. Visualize
Once you've set up data collection with OpenLIT, you can visualize and analyze this information to better understand your application's performance:
- **Using OpenLIT UI:** Connect to OpenLIT's UI to start exploring performance metrics. Visit the OpenLIT [Quickstart Guide](https://docs.openlit.io/latest/quickstart) for step-by-step details.
- **Integrate with existing Observability Tools:** If you use tools like Grafana or DataDog, you can integrate the data collected by OpenLIT. For instructions on setting up these connections, check the OpenLIT [Connections Guide](https://docs.openlit.io/latest/connections/intro).
+17 -25
View File
@@ -19,9 +19,7 @@
"modeToggle": {
"default": "dark"
},
"openapi": [
"/rest-api.json"
],
"openapi": ["/rest-api.json"],
"metadata": {
"og:image": "/images/og.png",
"twitter:site": "@embedchain"
@@ -69,7 +67,9 @@
"pages": [
"integration/langsmith",
"integration/chainlit",
"integration/streamlit-mistral"
"integration/streamlit-mistral",
"integration/openlit",
"integration/helicone"
]
}
]
@@ -131,13 +131,13 @@
{
"group": "🗄️ Vector databases",
"pages": [
"components/vector-databases/chromadb",
"components/vector-databases/elasticsearch",
"components/vector-databases/pinecone",
"components/vector-databases/opensearch",
"components/vector-databases/qdrant",
"components/vector-databases/weaviate",
"components/vector-databases/zilliz"
"components/vector-databases/chromadb",
"components/vector-databases/elasticsearch",
"components/vector-databases/pinecone",
"components/vector-databases/opensearch",
"components/vector-databases/qdrant",
"components/vector-databases/weaviate",
"components/vector-databases/zilliz"
]
},
"components/llms",
@@ -155,15 +155,12 @@
"deployment/railway",
"deployment/streamlit_io",
"deployment/gradio_app",
"deployment/huggingface_spaces",
"deployment/embedchain_ai"
"deployment/huggingface_spaces"
]
},
{
"group": "Community",
"pages": [
"community/connect-with-us"
]
"pages": ["community/connect-with-us"]
},
{
"group": "Examples",
@@ -203,9 +200,7 @@
},
{
"group": "Showcase",
"pages": [
"examples/showcase"
]
"pages": ["examples/showcase"]
},
{
"group": "API Reference",
@@ -236,15 +231,12 @@
"contribution/guidelines",
"contribution/dev",
"contribution/docs",
"contribution/python",
"contribution/javascript"
"contribution/python"
]
},
{
"group": "Product",
"pages": [
"product/release-notes"
]
"pages": ["product/release-notes"]
}
],
"footerSocials": {
@@ -282,4 +274,4 @@
"destination": "/get-started/introduction"
}
]
}
}
-2
View File
@@ -1,2 +0,0 @@
node_modules
dist
-56
View File
@@ -1,56 +0,0 @@
{
// Configuration for JavaScript files
"extends": [
"airbnb-base",
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"endOfLine": "auto"
}
]
},
"overrides": [
// Configuration for TypeScript files
{
"files": ["**/*.ts", "**/__tests__/*.test.ts"],
"plugins": [
"@typescript-eslint",
"unused-imports",
"simple-import-sort"
],
"extends": [
"airbnb-typescript",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"endOfLine": "auto"
}
],
"@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier
"@typescript-eslint/consistent-type-imports": "error", // Ensure `import type` is used when it's necessary
"import/prefer-default-export": "off", // Named export is easier to refactor automatically
"simple-import-sort/imports": "error", // Import configuration for `eslint-plugin-simple-import-sort`
"simple-import-sort/exports": "error", // Export configuration for `eslint-plugin-simple-import-sort`
"@typescript-eslint/no-unused-vars": "off",
"react/jsx-filename-extension": "off", // Gives error
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
]
}
}
]
}
-47
View File
@@ -1,47 +0,0 @@
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v3
with:
name: dist
path: dist
- uses: actions/upload-artifact@v3
with:
name: types
path: types
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- uses: actions/download-artifact@v3
with:
name: dist
path: dist
- uses: actions/download-artifact@v3
with:
name: types
path: types
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
-138
View File
@@ -1,138 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.ideas.md
.todos.md
# Custom
dist
types
build
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit $1
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Disable concurent to run `check-types` after ESLint in lint-staged
npx lint-staged --concurrent false
-8
View File
@@ -1,8 +0,0 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Singh"
given-names: "Taranjeet"
title: "Embedchain"
date-released: 2023-06-25
url: "https://github.com/embedchain/embedchainjs"
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-254
View File
@@ -1,254 +0,0 @@
# embedchainjs
[![Discord](https://dcbadge.vercel.app/api/server/CUU9FPhRNt?style=flat)](https://discord.gg/CUU9FPhRNt)
[![Twitter](https://img.shields.io/twitter/follow/embedchain)](https://twitter.com/embedchain)
[![Substack](https://img.shields.io/badge/Substack-%23006f5c.svg?logo=substack)](https://embedchain.substack.com/)
embedchain is a framework to easily create LLM powered bots over any dataset. embedchainjs is Javascript version of embedchain. If you want a python version, check out [embedchain-python](https://github.com/embedchain/embedchain)
# 🤝 Let's Talk Embedchain!
Schedule a [Feedback Session](https://cal.com/taranjeetio/ec) with Taranjeet, the founder, to discuss any issues, provide feedback, or explore improvements.
# How it works
It abstracts the entire process of loading dataset, chunking it, creating embeddings and then storing in vector database.
You can add a single or multiple dataset using `.add` and `.addLocal` function and then use `.query` function to find an answer from the added datasets.
If you want to create a Naval Ravikant bot which has 2 of his blog posts, as well as a question and answer pair you supply, all you need to do is add the links to the blog posts and the QnA pair and embedchain will create a bot for you.
```javascript
const dotenv = require("dotenv");
dotenv.config();
const { App } = require("embedchain");
//Run the app commands inside an async function only
async function testApp() {
const navalChatBot = await App();
// Embed Online Resources
await navalChatBot.add("web_page", "https://nav.al/feedback");
await navalChatBot.add("web_page", "https://nav.al/agi");
await navalChatBot.add(
"pdf_file",
"https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf"
);
// Embed Local Resources
await navalChatBot.addLocal("qna_pair", [
"Who is Naval Ravikant?",
"Naval Ravikant is an Indian-American entrepreneur and investor.",
]);
const result = await navalChatBot.query(
"What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"
);
console.log(result);
// answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
}
testApp();
```
# Getting Started
## Installation
- First make sure that you have the package installed. If not, then install it using `npm`
```bash
npm install embedchain && npm install -S openai@^3.3.0
```
- Currently, it is only compatible with openai 3.X, not the latest version 4.X. Please make sure to use the right version, otherwise you will see the `ChromaDB` error `TypeError: OpenAIApi.Configuration is not a constructor`
- Make sure that dotenv package is installed and your `OPENAI_API_KEY` in a file called `.env` in the root folder. You can install dotenv by
```js
npm install dotenv
```
- Download and install Docker on your device by visiting [this link](https://www.docker.com/). You will need this to run Chroma vector database on your machine.
- Run the following commands to setup Chroma container in Docker
```bash
git clone https://github.com/chroma-core/chroma.git
cd chroma
docker-compose up -d --build
```
- Once Chroma container has been set up, run it inside Docker
## Usage
- We use OpenAI's embedding model to create embeddings for chunks and ChatGPT API as LLM to get answer given the relevant docs. Make sure that you have an OpenAI account and an API key. If you have dont have an API key, you can create one by visiting [this link](https://platform.openai.com/account/api-keys).
- Once you have the API key, set it in an environment variable called `OPENAI_API_KEY`
```js
// Set this inside your .env file
OPENAI_API_KEY = "sk-xxxx";
```
- Load the environment variables inside your .js file using the following commands
```js
const dotenv = require("dotenv");
dotenv.config();
```
- Next import the `App` class from embedchain and use `.add` function to add any dataset.
- Now your app is created. You can use `.query` function to get the answer for any query.
```js
const dotenv = require("dotenv");
dotenv.config();
const { App } = require("embedchain");
async function testApp() {
const navalChatBot = await App();
// Embed Online Resources
await navalChatBot.add("web_page", "https://nav.al/feedback");
await navalChatBot.add("web_page", "https://nav.al/agi");
await navalChatBot.add(
"pdf_file",
"https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf"
);
// Embed Local Resources
await navalChatBot.addLocal("qna_pair", [
"Who is Naval Ravikant?",
"Naval Ravikant is an Indian-American entrepreneur and investor.",
]);
const result = await navalChatBot.query(
"What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"
);
console.log(result);
// answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
}
testApp();
```
- If there is any other app instance in your script or app, you can change the import as
```javascript
const { App: EmbedChainApp } = require("embedchain");
// or
const { App: ECApp } = require("embedchain");
```
## Format supported
We support the following formats:
### PDF File
To add any pdf file, use the data_type as `pdf_file`. Eg:
```javascript
await app.add("pdf_file", "a_valid_url_where_pdf_file_can_be_accessed");
```
### Web Page
To add any web page, use the data_type as `web_page`. Eg:
```javascript
await app.add("web_page", "a_valid_web_page_url");
```
### QnA Pair
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
```javascript
await app.addLocal("qna_pair", ["Question", "Answer"]);
```
### More Formats coming soon
- If you want to add any other format, please create an [issue](https://github.com/embedchain/embedchainjs/issues) and we will add it to the list of supported formats.
## Testing
Before you consume valuable tokens, you should make sure that the embedding you have done works and that it's receiving the correct document from the database.
For this you can use the `dryRun` method.
Following the example above, add this to your script:
```js
let result = await naval_chat_bot.dryRun("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?");console.log(result);
'''
Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
terms of the unseen. And I think that’s critical. That is what humans do uniquely that no other creature, no other computer, no other intelligence—biological or artificial—that we have ever encountered does. And not only do we do it uniquely, but if we were to meet an alien species that also had the power to generate these good explanations, there is no explanation that they could generate that we could not understand. We are maximally capable of understanding. There is no concept out there that is possible in this physical reality that a human being, given sufficient time and resources and
Query: What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?
Helpful Answer:
'''
```
_The embedding is confirmed to work as expected. It returns the right document, even if the question is asked slightly different. No prompt tokens have been consumed._
**The dry run will still consume tokens to embed your query, but it is only ~1/15 of the prompt.**
# How does it work?
Creating a chat bot over any dataset needs the following steps to happen
- load the data
- create meaningful chunks
- create embeddings for each chunk
- store the chunks in vector database
Whenever a user asks any query, following process happens to find the answer for the query
- create the embedding for query
- find similar documents for this query from vector database
- pass similar documents as context to LLM to get the final answer.
The process of loading the dataset and then querying involves multiple steps and each steps has nuances of it is own.
- How should I chunk the data? What is a meaningful chunk size?
- How should I create embeddings for each chunk? Which embedding model should I use?
- How should I store the chunks in vector database? Which vector database should I use?
- Should I store meta data along with the embeddings?
- How should I find similar documents for a query? Which ranking model should I use?
These questions may be trivial for some but for a lot of us, it needs research, experimentation and time to find out the accurate answers.
embedchain is a framework which takes care of all these nuances and provides a simple interface to create bots over any dataset.
In the first release, we are making it easier for anyone to get a chatbot over any dataset up and running in less than a minute. All you need to do is create an app instance, add the data sets using `.add` function and then use `.query` function to get the relevant answer.
# Team
## Author
- Taranjeet Singh ([@taranjeetio](https://twitter.com/taranjeetio))
## Maintainer
- [cachho](https://github.com/cachho)
- [sahilyadav902](https://github.com/sahilyadav902)
## Citation
If you utilize this repository, please consider citing it with:
```
@misc{embedchain,
author = {Taranjeet Singh},
title = {Embechain: Framework to easily create LLM powered bots over any dataset},
year = {2023},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/embedchain/embedchainjs}},
}
```
-1
View File
@@ -1 +0,0 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
@@ -1,66 +0,0 @@
import { EmbedChainApp } from '../embedchain';
const mockAdd = jest.fn();
const mockAddLocal = jest.fn();
const mockQuery = jest.fn();
jest.mock('../embedchain', () => {
return {
EmbedChainApp: jest.fn().mockImplementation(() => {
return {
add: mockAdd,
addLocal: mockAddLocal,
query: mockQuery,
};
}),
};
});
describe('Test App', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('tests the App', async () => {
mockQuery.mockResolvedValue(
'Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.'
);
const navalChatBot = await new EmbedChainApp(undefined, false);
// Embed Online Resources
await navalChatBot.add('web_page', 'https://nav.al/feedback');
await navalChatBot.add('web_page', 'https://nav.al/agi');
await navalChatBot.add(
'pdf_file',
'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf'
);
// Embed Local Resources
await navalChatBot.addLocal('qna_pair', [
'Who is Naval Ravikant?',
'Naval Ravikant is an Indian-American entrepreneur and investor.',
]);
const result = await navalChatBot.query(
'What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?'
);
expect(mockAdd).toHaveBeenCalledWith('web_page', 'https://nav.al/feedback');
expect(mockAdd).toHaveBeenCalledWith('web_page', 'https://nav.al/agi');
expect(mockAdd).toHaveBeenCalledWith(
'pdf_file',
'https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf'
);
expect(mockAddLocal).toHaveBeenCalledWith('qna_pair', [
'Who is Naval Ravikant?',
'Naval Ravikant is an Indian-American entrepreneur and investor.',
]);
expect(mockQuery).toHaveBeenCalledWith(
'What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?'
);
expect(result).toBe(
'Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.'
);
});
});
@@ -1,44 +0,0 @@
import { createHash } from 'crypto';
import type { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import type { BaseLoader } from '../loaders';
import type { Input, LoaderResult } from '../models';
import type { ChunkResult } from '../models/ChunkResult';
class BaseChunker {
textSplitter: RecursiveCharacterTextSplitter;
constructor(textSplitter: RecursiveCharacterTextSplitter) {
this.textSplitter = textSplitter;
}
async createChunks(loader: BaseLoader, url: Input): Promise<ChunkResult> {
const documents: ChunkResult['documents'] = [];
const ids: ChunkResult['ids'] = [];
const datas: LoaderResult = await loader.loadData(url);
const metadatas: ChunkResult['metadatas'] = [];
const dataPromises = datas.map(async (data) => {
const { content, metaData } = data;
const chunks: string[] = await this.textSplitter.splitText(content);
chunks.forEach((chunk) => {
const chunkId = createHash('sha256')
.update(chunk + metaData.url)
.digest('hex');
ids.push(chunkId);
documents.push(chunk);
metadatas.push(metaData);
});
});
await Promise.all(dataPromises);
return {
documents,
ids,
metadatas,
};
}
}
export { BaseChunker };
@@ -1,26 +0,0 @@
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { BaseChunker } from './BaseChunker';
interface TextSplitterChunkParams {
chunkSize: number;
chunkOverlap: number;
keepSeparator: boolean;
}
const TEXT_SPLITTER_CHUNK_PARAMS: TextSplitterChunkParams = {
chunkSize: 1000,
chunkOverlap: 0,
keepSeparator: false,
};
class PdfFileChunker extends BaseChunker {
constructor() {
const textSplitter = new RecursiveCharacterTextSplitter(
TEXT_SPLITTER_CHUNK_PARAMS
);
super(textSplitter);
}
}
export { PdfFileChunker };
@@ -1,26 +0,0 @@
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { BaseChunker } from './BaseChunker';
interface TextSplitterChunkParams {
chunkSize: number;
chunkOverlap: number;
keepSeparator: boolean;
}
const TEXT_SPLITTER_CHUNK_PARAMS: TextSplitterChunkParams = {
chunkSize: 300,
chunkOverlap: 0,
keepSeparator: false,
};
class QnaPairChunker extends BaseChunker {
constructor() {
const textSplitter = new RecursiveCharacterTextSplitter(
TEXT_SPLITTER_CHUNK_PARAMS
);
super(textSplitter);
}
}
export { QnaPairChunker };
@@ -1,26 +0,0 @@
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { BaseChunker } from './BaseChunker';
interface TextSplitterChunkParams {
chunkSize: number;
chunkOverlap: number;
keepSeparator: boolean;
}
const TEXT_SPLITTER_CHUNK_PARAMS: TextSplitterChunkParams = {
chunkSize: 500,
chunkOverlap: 0,
keepSeparator: false,
};
class WebPageChunker extends BaseChunker {
constructor() {
const textSplitter = new RecursiveCharacterTextSplitter(
TEXT_SPLITTER_CHUNK_PARAMS
);
super(textSplitter);
}
}
export { WebPageChunker };
@@ -1,6 +0,0 @@
import { BaseChunker } from './BaseChunker';
import { PdfFileChunker } from './PdfFile';
import { QnaPairChunker } from './QnaPair';
import { WebPageChunker } from './WebPage';
export { BaseChunker, PdfFileChunker, QnaPairChunker, WebPageChunker };
-317
View File
@@ -1,317 +0,0 @@
/* eslint-disable max-classes-per-file */
import type { Collection } from 'chromadb';
import type { QueryResponse } from 'chromadb/dist/main/types';
import * as fs from 'fs';
import { Document } from 'langchain/document';
import OpenAI from 'openai';
import * as path from 'path';
import { v4 as uuidv4 } from 'uuid';
import type { BaseChunker } from './chunkers';
import { PdfFileChunker, QnaPairChunker, WebPageChunker } from './chunkers';
import type { BaseLoader } from './loaders';
import { LocalQnaPairLoader, PdfFileLoader, WebPageLoader } from './loaders';
import type {
DataDict,
DataType,
FormattedResult,
Input,
LocalInput,
Metadata,
Method,
RemoteInput,
} from './models';
import { ChromaDB } from './vectordb';
import type { BaseVectorDB } from './vectordb/BaseVectorDb';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
class EmbedChain {
dbClient: any;
// TODO: Definitely assign
collection!: Collection;
userAsks: [DataType, Input][] = [];
initApp: Promise<void>;
collectMetrics: boolean;
sId: string; // sessionId
constructor(db?: BaseVectorDB, collectMetrics: boolean = true) {
if (!db) {
this.initApp = this.setupChroma();
} else {
this.initApp = this.setupOther(db);
}
this.collectMetrics = collectMetrics;
// Send anonymous telemetry
this.sId = uuidv4();
this.sendTelemetryEvent('init');
}
async setupChroma(): Promise<void> {
const db = new ChromaDB();
await db.initDb;
this.dbClient = db.client;
if (db.collection) {
this.collection = db.collection;
} else {
// TODO: Add proper error handling
console.error('No collection');
}
}
async setupOther(db: BaseVectorDB): Promise<void> {
await db.initDb;
// TODO: Figure out how we can initialize an unknown database.
// this.dbClient = db.client;
// this.collection = db.collection;
this.userAsks = [];
}
static getLoader(dataType: DataType) {
const loaders: { [t in DataType]: BaseLoader } = {
pdf_file: new PdfFileLoader(),
web_page: new WebPageLoader(),
qna_pair: new LocalQnaPairLoader(),
};
return loaders[dataType];
}
static getChunker(dataType: DataType) {
const chunkers: { [t in DataType]: BaseChunker } = {
pdf_file: new PdfFileChunker(),
web_page: new WebPageChunker(),
qna_pair: new QnaPairChunker(),
};
return chunkers[dataType];
}
public async add(dataType: DataType, url: RemoteInput) {
const loader = EmbedChain.getLoader(dataType);
const chunker = EmbedChain.getChunker(dataType);
this.userAsks.push([dataType, url]);
const { documents, countNewChunks } = await this.loadAndEmbed(
loader,
chunker,
url
);
if (this.collectMetrics) {
const wordCount = documents.reduce(
(sum, document) => sum + document.split(' ').length,
0
);
this.sendTelemetryEvent('add', {
data_type: dataType,
word_count: wordCount,
chunks_count: countNewChunks,
});
}
}
public async addLocal(dataType: DataType, content: LocalInput) {
const loader = EmbedChain.getLoader(dataType);
const chunker = EmbedChain.getChunker(dataType);
this.userAsks.push([dataType, content]);
const { documents, countNewChunks } = await this.loadAndEmbed(
loader,
chunker,
content
);
if (this.collectMetrics) {
const wordCount = documents.reduce(
(sum, document) => sum + document.split(' ').length,
0
);
this.sendTelemetryEvent('add_local', {
data_type: dataType,
word_count: wordCount,
chunks_count: countNewChunks,
});
}
}
protected async loadAndEmbed(
loader: any,
chunker: BaseChunker,
src: Input
): Promise<{
documents: string[];
metadatas: Metadata[];
ids: string[];
countNewChunks: number;
}> {
const embeddingsData = await chunker.createChunks(loader, src);
let { documents, ids, metadatas } = embeddingsData;
const existingDocs = await this.collection.get({ ids });
const existingIds = new Set(existingDocs.ids);
if (existingIds.size > 0) {
const dataDict: DataDict = {};
for (let i = 0; i < ids.length; i += 1) {
const id = ids[i];
if (!existingIds.has(id)) {
dataDict[id] = { doc: documents[i], meta: metadatas[i] };
}
}
if (Object.keys(dataDict).length === 0) {
console.log(`All data from ${src} already exists in the database.`);
return { documents: [], metadatas: [], ids: [], countNewChunks: 0 };
}
ids = Object.keys(dataDict);
const dataValues = Object.values(dataDict);
documents = dataValues.map(({ doc }) => doc);
metadatas = dataValues.map(({ meta }) => meta);
}
const countBeforeAddition = await this.count();
await this.collection.add({ documents, metadatas, ids });
const countNewChunks = (await this.count()) - countBeforeAddition;
console.log(
`Successfully saved ${src}. New chunks count: ${countNewChunks}`
);
return { documents, metadatas, ids, countNewChunks };
}
static async formatResult(
results: QueryResponse
): Promise<FormattedResult[]> {
return results.documents[0].map((document: any, index: number) => {
const metadata = results.metadatas[0][index] || {};
// TODO: Add proper error handling
const distance = results.distances ? results.distances[0][index] : null;
return [new Document({ pageContent: document, metadata }), distance];
});
}
static async getOpenAiAnswer(prompt: string) {
const messages: OpenAI.Chat.CreateChatCompletionRequestMessage[] = [
{ role: 'user', content: prompt },
];
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages,
temperature: 0,
max_tokens: 1000,
top_p: 1,
});
return (
response.choices[0].message?.content ?? 'Response could not be processed.'
);
}
protected async retrieveFromDatabase(inputQuery: string) {
const result = await this.collection.query({
nResults: 1,
queryTexts: [inputQuery],
});
const resultFormatted = await EmbedChain.formatResult(result);
const content = resultFormatted[0][0].pageContent;
return content;
}
static generatePrompt(inputQuery: string, context: any) {
const prompt = `Use the following pieces of context to answer the query at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.\n${context}\nQuery: ${inputQuery}\nHelpful Answer:`;
return prompt;
}
static async getAnswerFromLlm(prompt: string) {
const answer = await EmbedChain.getOpenAiAnswer(prompt);
return answer;
}
public async query(inputQuery: string) {
const context = await this.retrieveFromDatabase(inputQuery);
const prompt = EmbedChain.generatePrompt(inputQuery, context);
const answer = await EmbedChain.getAnswerFromLlm(prompt);
this.sendTelemetryEvent('query');
return answer;
}
public async dryRun(input_query: string) {
const context = await this.retrieveFromDatabase(input_query);
const prompt = EmbedChain.generatePrompt(input_query, context);
return prompt;
}
/**
* Count the number of embeddings.
* @returns {Promise<number>}: The number of embeddings.
*/
public count(): Promise<number> {
return this.collection.count();
}
protected async sendTelemetryEvent(method: Method, extraMetadata?: object) {
if (!this.collectMetrics) {
return;
}
const url = 'https://api.embedchain.ai/api/v1/telemetry/';
// Read package version from filesystem (because it's not in the ts root dir)
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const metadata = {
s_id: this.sId,
version: packageJson.version,
method,
language: 'js',
...extraMetadata,
};
const maxRetries = 3;
// Retry the fetch
for (let i = 0; i < maxRetries; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({ metadata }),
});
if (response.ok) {
// Break out of the loop if the request was successful
break;
} else {
// Log the unsuccessful response (optional)
console.error(
`Telemetry: Attempt ${i + 1} failed with status:`,
response.status
);
}
} catch (error) {
// Log the error (optional)
console.error(`Telemetry: Attempt ${i + 1} failed with error:`, error);
}
// If this was the last attempt, throw an error or handle the failure
if (i === maxRetries - 1) {
console.error('Telemetry: Max retries reached');
}
}
}
}
class EmbedChainApp extends EmbedChain {
// The EmbedChain app.
// Has two functions: add and query.
// adds(dataType, url): adds the data from the given URL to the vector db.
// query(query): finds answer to the given query using vector database and LLM.
}
export { EmbedChainApp };
-7
View File
@@ -1,7 +0,0 @@
import { EmbedChainApp } from './embedchain';
export const App = async () => {
const app = new EmbedChainApp();
await app.initApp;
return app;
};
@@ -1,5 +0,0 @@
import type { Input, LoaderResult } from '../models';
export abstract class BaseLoader {
abstract loadData(src: Input): Promise<LoaderResult>;
}
@@ -1,21 +0,0 @@
import type { LoaderResult, QnaPair } from '../models';
import { BaseLoader } from './BaseLoader';
class LocalQnaPairLoader extends BaseLoader {
// eslint-disable-next-line class-methods-use-this
async loadData(content: QnaPair): Promise<LoaderResult> {
const [question, answer] = content;
const contentText = `Q: ${question}\nA: ${answer}`;
const metaData = {
url: 'local',
};
return [
{
content: contentText,
metaData,
},
];
}
}
export { LocalQnaPairLoader };
@@ -1,58 +0,0 @@
import type { TextContent } from 'pdfjs-dist/types/src/display/api';
import type { LoaderResult, Metadata } from '../models';
import { cleanString } from '../utils';
import { BaseLoader } from './BaseLoader';
const pdfjsLib = require('pdfjs-dist');
interface Page {
page_content: string;
}
class PdfFileLoader extends BaseLoader {
static async getPagesFromPdf(url: string): Promise<Page[]> {
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
const { numPages } = pdf;
const promises = Array.from({ length: numPages }, async (_, i) => {
const page = await pdf.getPage(i + 1);
const pageText: TextContent = await page.getTextContent();
const pageContent: string = pageText.items
.map((item) => ('str' in item ? item.str : ''))
.join(' ');
return {
page_content: pageContent,
};
});
return Promise.all(promises);
}
// eslint-disable-next-line class-methods-use-this
async loadData(url: string): Promise<LoaderResult> {
const pages: Page[] = await PdfFileLoader.getPagesFromPdf(url);
const output: LoaderResult = [];
if (!pages.length) {
throw new Error('No data found');
}
pages.forEach((page) => {
let content: string = page.page_content;
content = cleanString(content);
const metaData: Metadata = {
url,
};
output.push({
content,
metaData,
});
});
return output;
}
}
export { PdfFileLoader };
@@ -1,51 +0,0 @@
import axios from 'axios';
import { JSDOM } from 'jsdom';
import { cleanString } from '../utils';
import { BaseLoader } from './BaseLoader';
class WebPageLoader extends BaseLoader {
// eslint-disable-next-line class-methods-use-this
async loadData(url: string) {
const response = await axios.get(url);
const html = response.data;
const dom = new JSDOM(html);
const { document } = dom.window;
const unwantedTags = [
'nav',
'aside',
'form',
'header',
'noscript',
'svg',
'canvas',
'footer',
'script',
'style',
];
unwantedTags.forEach((tagName) => {
const elements = document.getElementsByTagName(tagName);
Array.from(elements).forEach((element) => {
// eslint-disable-next-line no-param-reassign
(element as HTMLElement).textContent = ' ';
});
});
const output = [];
let content = document.body.textContent;
if (!content) {
throw new Error('Web page content is empty.');
}
content = cleanString(content);
const metaData = {
url,
};
output.push({
content,
metaData,
});
return output;
}
}
export { WebPageLoader };
@@ -1,6 +0,0 @@
import { BaseLoader } from './BaseLoader';
import { LocalQnaPairLoader } from './LocalQnaPair';
import { PdfFileLoader } from './PdfFile';
import { WebPageLoader } from './WebPage';
export { BaseLoader, LocalQnaPairLoader, PdfFileLoader, WebPageLoader };
@@ -1,7 +0,0 @@
import type { Metadata } from './Metadata';
export type ChunkResult = {
documents: string[];
ids: string[];
metadatas: Metadata[];
};
@@ -1,10 +0,0 @@
import type { ChunkResult } from './ChunkResult';
type Data = {
doc: ChunkResult['documents'][0];
meta: ChunkResult['metadatas'][0];
};
export type DataDict = {
[id: string]: Data;
};
@@ -1 +0,0 @@
export type DataType = 'pdf_file' | 'web_page' | 'qna_pair';
@@ -1,3 +0,0 @@
import type { Document } from 'langchain/document';
export type FormattedResult = [Document, number | null];
-7
View File
@@ -1,7 +0,0 @@
import type { QnaPair } from './QnAPair';
export type RemoteInput = string;
export type LocalInput = QnaPair;
export type Input = RemoteInput | LocalInput;
@@ -1,3 +0,0 @@
import type { Metadata } from './Metadata';
export type LoaderResult = { content: any; metaData: Metadata }[];
@@ -1,3 +0,0 @@
export type Metadata = {
url: string;
};
@@ -1 +0,0 @@
export type Method = 'init' | 'query' | 'add' | 'add_local';
@@ -1,4 +0,0 @@
type Question = string;
type Answer = string;
export type QnaPair = [Question, Answer];
-21
View File
@@ -1,21 +0,0 @@
import { DataDict } from './DataDict';
import { DataType } from './DataType';
import { FormattedResult } from './FormattedResult';
import { Input, LocalInput, RemoteInput } from './Input';
import { LoaderResult } from './LoaderResult';
import { Metadata } from './Metadata';
import { Method } from './Method';
import { QnaPair } from './QnAPair';
export {
DataDict,
DataType,
FormattedResult,
Input,
LoaderResult,
LocalInput,
Metadata,
Method,
QnaPair,
RemoteInput,
};
-26
View File
@@ -1,26 +0,0 @@
/**
* This function takes in a string and performs a series of text cleaning operations.
* @param {str} text: The text to be cleaned. This is expected to be a string.
* @returns {str}: The cleaned text after all the cleaning operations have been performed.
*/
export function cleanString(text: string): string {
// Replacement of newline characters:
let cleanedText = text.replace(/\n/g, ' ');
// Stripping and reducing multiple spaces to single:
cleanedText = cleanedText.trim().replace(/\s+/g, ' ');
// Removing backslashes:
cleanedText = cleanedText.replace(/\\/g, '');
// Replacing hash characters:
cleanedText = cleanedText.replace(/#/g, ' ');
// Eliminating consecutive non-alphanumeric characters:
// This regex identifies consecutive non-alphanumeric characters (i.e., not a word character [a-zA-Z0-9_] and not a whitespace) in the string
// and replaces each group of such characters with a single occurrence of that character.
// For example, "!!! hello !!!" would become "! hello !".
cleanedText = cleanedText.replace(/([^\w\s])\1*/g, '$1');
return cleanedText;
}
@@ -1,14 +0,0 @@
class BaseVectorDB {
initDb: Promise<void>;
constructor() {
this.initDb = this.getClientAndCollection();
}
// eslint-disable-next-line class-methods-use-this
protected async getClientAndCollection(): Promise<void> {
throw new Error('getClientAndCollection() method is not implemented');
}
}
export { BaseVectorDB };
@@ -1,38 +0,0 @@
import type { Collection } from 'chromadb';
import { ChromaClient, OpenAIEmbeddingFunction } from 'chromadb';
import { BaseVectorDB } from './BaseVectorDb';
const embedder = new OpenAIEmbeddingFunction({
openai_api_key: process.env.OPENAI_API_KEY ?? '',
});
class ChromaDB extends BaseVectorDB {
client: ChromaClient | undefined;
collection: Collection | null = null;
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor() {
super();
}
protected async getClientAndCollection(): Promise<void> {
this.client = new ChromaClient({ path: 'http://localhost:8000' });
try {
this.collection = await this.client.getCollection({
name: 'embedchain_store',
embeddingFunction: embedder,
});
} catch (err) {
if (!this.collection) {
this.collection = await this.client.createCollection({
name: 'embedchain_store',
embeddingFunction: embedder,
});
}
}
}
}
export { ChromaDB };
@@ -1,3 +0,0 @@
import { ChromaDB } from './ChromaDb';
export { ChromaDB };
-9
View File
@@ -1,9 +0,0 @@
const { EmbedChainApp } = require("./embedchain/embedchain");
async function App() {
const app = new EmbedChainApp();
await app.init_app;
return app;
}
module.exports = { App };
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['.d.ts'],
};
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
'*.{js,ts}': ['eslint --fix', 'eslint'],
'**/*.ts?(x)': () => 'npm run check-types',
'*.json': ['prettier --write'],
};
-18457
View File
File diff suppressed because it is too large Load Diff
-53
View File
@@ -1,53 +0,0 @@
{
"name": "embedchain",
"version": "0.0.8",
"description": "embedchain is a framework to easily create LLM powered bots over any dataset",
"main": "dist/index.js",
"types": "types/index.d.ts",
"files": [
"dist",
"types"
],
"scripts": {
"build": "tsc -p tsconfig.build.json --listFiles",
"prepare": "husky install",
"test": "jest",
"check-types": "tsc --noEmit --pretty"
},
"author": "Taranjeet Singh",
"license": "Apache-2.0",
"dependencies": {
"axios": "^1.4.0",
"chromadb": "^1.5.6",
"jsdom": "^22.1.0",
"langchain": "^0.0.136",
"openai": "^4.3.1",
"pdfjs-dist": "^3.8.162",
"uuid": "^9.0.0"
},
"devDependencies": {
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@commitlint/cz-commitlint": "^17.1.2",
"@types/jest": "^29.5.1",
"@types/jsdom": "^21.1.1",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"eslint": "^8.34.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-testing-library": "^5.9.1",
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^8.0.1",
"jest": "^29.5.0",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.2",
"typescript": "^5.2.2"
}
}
-4
View File
@@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["embedchain/__tests__"]
}
-15
View File
@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"module": "CommonJS",
"strict": true,
"outDir": "dist",
"rootDir": "embedchain",
"sourceMap": true,
"declaration": true,
"declarationDir": "types",
"esModuleInterop": true
},
"include": ["embedchain/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
+1
View File
@@ -91,6 +91,7 @@ keys = console
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
+21 -11
View File
@@ -9,19 +9,24 @@ import requests
import yaml
from tqdm import tqdm
from embedchain.cache import (Config, ExactMatchEvaluation,
SearchDistanceEvaluation, cache,
gptcache_data_manager, gptcache_pre_function)
from mem0 import Mem0
from embedchain.cache import (
Config,
ExactMatchEvaluation,
SearchDistanceEvaluation,
cache,
gptcache_data_manager,
gptcache_pre_function,
)
from embedchain.client import Client
from embedchain.config import AppConfig, CacheConfig, ChunkerConfig
from embedchain.config import AppConfig, CacheConfig, ChunkerConfig, Mem0Config
from embedchain.core.db.database import get_session, init_db, setup_engine
from embedchain.core.db.models import DataSource
from embedchain.embedchain import EmbedChain
from embedchain.embedder.base import BaseEmbedder
from embedchain.embedder.openai import OpenAIEmbedder
from embedchain.evaluation.base import BaseMetric
from embedchain.evaluation.metrics import (AnswerRelevance, ContextRelevance,
Groundedness)
from embedchain.evaluation.metrics import AnswerRelevance, ContextRelevance, Groundedness
from embedchain.factory import EmbedderFactory, LlmFactory, VectorDBFactory
from embedchain.helpers.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
@@ -55,6 +60,7 @@ class App(EmbedChain):
auto_deploy: bool = False,
chunker: ChunkerConfig = None,
cache_config: CacheConfig = None,
memory_config: Mem0Config = None,
log_level: int = logging.WARN,
):
"""
@@ -83,12 +89,10 @@ class App(EmbedChain):
if name and config:
raise Exception("Cannot provide both name and config. Please provide only one of them.")
logger.debug("4.0")
# Initialize the metadata db for the app
setup_engine(database_uri=os.environ.get("EMBEDCHAIN_DB_URI"))
init_db()
logger.debug("4.0")
self.auto_deploy = auto_deploy
# Store the dict config as an attribute to be able to send it
self.config_data = config_data if (config_data and validate_config(config_data)) else None
@@ -97,6 +101,7 @@ class App(EmbedChain):
self.id = None
self.chunker = ChunkerConfig(**chunker) if chunker else None
self.cache_config = cache_config
self.memory_config = memory_config
self.config = config or AppConfig()
self.name = self.config.name
@@ -118,7 +123,6 @@ class App(EmbedChain):
self.llm = llm or OpenAILlm()
self._init_db()
logger.debug("4.1")
# Session for the metadata db
self.db_session = get_session()
@@ -126,7 +130,11 @@ class App(EmbedChain):
if self.cache_config is not None:
self._init_cache()
logger.debug("4.2")
# If memory_config is provided, initializing the memory ...
self.mem0_client = None
if self.memory_config is not None:
self.mem0_client = Mem0(api_key=self.memory_config.api_key)
# Send anonymous telemetry
self._telemetry_props = {"class": self.__class__.__name__}
self.telemetry = AnonymousTelemetry(enabled=self.config.collect_metrics)
@@ -337,7 +345,6 @@ class App(EmbedChain):
:return: An instance of the App class.
:rtype: App
"""
logger.debug("6")
# Backward compatibility for yaml_path
if yaml_path and not config_path:
config_path = yaml_path
@@ -370,11 +377,13 @@ class App(EmbedChain):
app_config_data = config_data.get("app", {}).get("config", {})
vector_db_config_data = config_data.get("vectordb", {})
embedding_model_config_data = config_data.get("embedding_model", config_data.get("embedder", {}))
memory_config_data = config_data.get("memory", {})
llm_config_data = config_data.get("llm", {})
chunker_config_data = config_data.get("chunker", {})
cache_config_data = config_data.get("cache", None)
app_config = AppConfig(**app_config_data)
memory_config = Mem0Config(**memory_config_data) if memory_config_data else None
vector_db_provider = vector_db_config_data.get("provider", "chroma")
vector_db = VectorDBFactory.create(vector_db_provider, vector_db_config_data.get("config", {}))
@@ -408,6 +417,7 @@ class App(EmbedChain):
auto_deploy=auto_deploy,
chunker=chunker_config_data,
cache_config=cache_config,
memory_config=memory_config,
)
def _eval(self, dataset: list[EvalData], metric: Union[BaseMetric, str]):
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.add_config import ChunkerConfig
from embedchain.helpers.json_serializable import register_deserializable
@register_deserializable
class AudioChunker(BaseChunker):
"""Chunker for audio."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+1 -1
View File
@@ -84,4 +84,4 @@ class BaseChunker(JSONSerializable):
@staticmethod
def get_word_count(documents) -> int:
return sum([len(document.split(" ")) for document in documents])
return sum(len(document.split(" ")) for document in documents)
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config.add_config import ChunkerConfig
from embedchain.helpers.json_serializable import register_deserializable
@register_deserializable
class ExcelFileChunker(BaseChunker):
"""Chunker for Excel file."""
def __init__(self, config: Optional[ChunkerConfig] = None):
if config is None:
config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=config.chunk_size,
chunk_overlap=config.chunk_overlap,
length_function=config.length_function,
)
super().__init__(text_splitter)
+2
View File
@@ -6,8 +6,10 @@ from .base_config import BaseConfig
from .cache_config import CacheConfig
from .embedder.base import BaseEmbedderConfig
from .embedder.base import BaseEmbedderConfig as EmbedderConfig
from .embedder.ollama import OllamaEmbedderConfig
from .llm.base import BaseLlmConfig
from .vectordb.chroma import ChromaDbConfig
from .vectordb.elasticsearch import ElasticsearchDBConfig
from .vectordb.opensearch import OpenSearchDBConfig
from .vectordb.zilliz import ZillizDBConfig
from .mem0_config import Mem0Config
+15 -1
View File
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Any, Dict, Optional
from embedchain.helpers.json_serializable import register_deserializable
@@ -10,8 +10,10 @@ class BaseEmbedderConfig:
model: Optional[str] = None,
deployment_name: Optional[str] = None,
vector_dimension: Optional[int] = None,
endpoint: Optional[str] = None,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
model_kwargs: Optional[Dict[str, Any]] = None,
):
"""
Initialize a new instance of an embedder config class.
@@ -20,9 +22,21 @@ class BaseEmbedderConfig:
:type model: Optional[str], optional
:param deployment_name: deployment name for llm embedding model, defaults to None
:type deployment_name: Optional[str], optional
:param vector_dimension: vector dimension of the embedding model, defaults to None
:type vector_dimension: Optional[int], optional
:param endpoint: endpoint for the embedding model, defaults to None
:type endpoint: Optional[str], optional
:param api_key: hugginface api key, defaults to None
:type api_key: Optional[str], optional
:param api_base: huggingface api base, defaults to None
:type api_base: Optional[str], optional
:param model_kwargs: key-value arguments for the embedding model, defaults a dict inside init.
:type model_kwargs: Optional[Dict[str, Any]], defaults a dict inside init.
"""
self.model = model
self.deployment_name = deployment_name
self.vector_dimension = vector_dimension
self.endpoint = endpoint
self.api_key = api_key
self.api_base = api_base
self.model_kwargs = model_kwargs or {}
+2 -1
View File
@@ -10,9 +10,10 @@ class GoogleAIEmbedderConfig(BaseEmbedderConfig):
self,
model: Optional[str] = None,
deployment_name: Optional[str] = None,
vector_dimension: Optional[int] = None,
task_type: Optional[str] = None,
title: Optional[str] = None,
):
super().__init__(model, deployment_name)
super().__init__(model, deployment_name, vector_dimension)
self.task_type = task_type or "retrieval_document"
self.title = title or "Embeddings for Embedchain"
+16
View File
@@ -0,0 +1,16 @@
from typing import Optional
from embedchain.config.embedder.base import BaseEmbedderConfig
from embedchain.helpers.json_serializable import register_deserializable
@register_deserializable
class OllamaEmbedderConfig(BaseEmbedderConfig):
def __init__(
self,
model: Optional[str] = None,
base_url: Optional[str] = None,
vector_dimension: Optional[int] = None,
):
super().__init__(model=model, vector_dimension=vector_dimension)
self.base_url = base_url or "http://localhost:11434"
+64 -1
View File
@@ -1,7 +1,10 @@
import json
import logging
import re
from string import Template
from typing import Any, Optional
from typing import Any, Mapping, Optional, Dict, Union
import httpx
from embedchain.config.base_config import BaseConfig
from embedchain.helpers.json_serializable import register_deserializable
@@ -47,6 +50,35 @@ Query: $query
Answer:
""" # noqa:E501
DEFAULT_PROMPT_WITH_MEM0_MEMORY = """
You are a Q&A expert system. Your responses must always be rooted in the context provided for each query. You are also provided with the conversation history and memories with the user. Make sure to use relevant context from conversation history and memories as needed.
Here are some guidelines to follow:
1. Refrain from explicitly mentioning the context provided in your response.
2. Take into consideration the conversation history and memories provided.
3. The context should silently guide your answers without being directly acknowledged.
4. Do not use phrases such as 'According to the context provided', 'Based on the context, ...' etc.
Context information:
----------------------
$context
----------------------
Conversation history:
----------------------
$history
----------------------
Memories/Preferences:
----------------------
$memories
----------------------
Query: $query
Answer:
""" # noqa:E501
DOCS_SITE_DEFAULT_PROMPT = """
You are an expert AI assistant for developer support product. Your responses must always be rooted in the context provided for each query. Wherever possible, give complete code snippet. Dont make up any code snippet on your own.
@@ -67,6 +99,7 @@ Answer:
DEFAULT_PROMPT_TEMPLATE = Template(DEFAULT_PROMPT)
DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE = Template(DEFAULT_PROMPT_WITH_HISTORY)
DEFAULT_PROMPT_WITH_MEM0_MEMORY_TEMPLATE = Template(DEFAULT_PROMPT_WITH_MEM0_MEMORY)
DOCS_SITE_PROMPT_TEMPLATE = Template(DOCS_SITE_DEFAULT_PROMPT)
query_re = re.compile(r"\$\{*query\}*")
context_re = re.compile(r"\$\{*context\}*")
@@ -89,6 +122,8 @@ class BaseLlmConfig(BaseConfig):
max_tokens: int = 1000,
top_p: float = 1,
stream: bool = False,
online: bool = False,
token_usage: bool = False,
deployment_name: Optional[str] = None,
system_prompt: Optional[str] = None,
where: dict[str, Any] = None,
@@ -98,7 +133,11 @@ class BaseLlmConfig(BaseConfig):
base_url: Optional[str] = None,
endpoint: Optional[str] = None,
model_kwargs: Optional[dict[str, Any]] = None,
http_client_proxies: Optional[Union[Dict, str]] = None,
http_async_client_proxies: Optional[Union[Dict, str]] = None,
local: Optional[bool] = False,
default_headers: Optional[Mapping[str, str]] = None,
api_version: Optional[str] = None,
):
"""
Initializes a configuration class instance for the LLM.
@@ -126,6 +165,10 @@ class BaseLlmConfig(BaseConfig):
:type top_p: float, optional
:param stream: Control if response is streamed back to user, defaults to False
:type stream: bool, optional
:param online: Controls whether to use internet for answering query, defaults to False
:type online: bool, optional
:param token_usage: Controls whether to return token usage in response, defaults to False
:type token_usage: bool, optional
:param deployment_name: t.b.a., defaults to None
:type deployment_name: Optional[str], optional
:param system_prompt: System prompt string, defaults to None
@@ -142,8 +185,15 @@ class BaseLlmConfig(BaseConfig):
:type callbacks: Optional[list], optional
:param query_type: The type of query to use, defaults to None
:type query_type: Optional[str], optional
:param http_client_proxies: The proxy server settings used to create self.http_client, defaults to None
:type http_client_proxies: Optional[Dict | str], optional
:param http_async_client_proxies: The proxy server settings for async calls used to create
self.http_async_client, defaults to None
:type http_async_client_proxies: Optional[Dict | str], optional
:param local: If True, the model will be run locally, defaults to False (for huggingface provider)
:type local: Optional[bool], optional
:param default_headers: Set additional HTTP headers to be sent with requests to OpenAI
:type default_headers: Optional[Mapping[str, str]], optional
:raises ValueError: If the template is not valid as template should
contain $context and $query (and optionally $history)
:raises ValueError: Stream is not boolean
@@ -164,6 +214,8 @@ class BaseLlmConfig(BaseConfig):
self.max_tokens = max_tokens
self.model = model
self.top_p = top_p
self.online = online
self.token_usage = token_usage
self.deployment_name = deployment_name
self.system_prompt = system_prompt
self.query_type = query_type
@@ -172,7 +224,18 @@ class BaseLlmConfig(BaseConfig):
self.base_url = base_url
self.endpoint = endpoint
self.model_kwargs = model_kwargs
self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None
self.http_async_client = (
httpx.AsyncClient(proxies=http_async_client_proxies) if http_async_client_proxies else None
)
self.local = local
self.default_headers = default_headers
self.online = online
self.api_version = api_version
if token_usage:
f = open("model_prices_and_context_window.json")
self.model_pricing_map = json.load(f)
if isinstance(prompt, str):
prompt = Template(prompt)
+21
View File
@@ -0,0 +1,21 @@
from typing import Any, Optional
from embedchain.config.base_config import BaseConfig
from embedchain.helpers.json_serializable import register_deserializable
@register_deserializable
class Mem0Config(BaseConfig):
def __init__(self, api_key: str, top_k: Optional[int] = 10):
self.api_key = api_key
self.top_k = top_k
@staticmethod
def from_config(config: Optional[dict[str, Any]]):
if config is None:
return Mem0Config()
else:
return Mem0Config(
api_key=config.get("api_key", ""),
init_config=config.get("top_k", 10),
)
+4
View File
@@ -12,6 +12,7 @@ class ChromaDbConfig(BaseVectorDbConfig):
dir: Optional[str] = None,
host: Optional[str] = None,
port: Optional[str] = None,
batch_size: Optional[int] = 100,
allow_reset=False,
chroma_settings: Optional[dict] = None,
):
@@ -26,6 +27,8 @@ class ChromaDbConfig(BaseVectorDbConfig):
:type host: Optional[str], optional
:param port: Database connection remote port. Use this if you run Embedchain as a client, defaults to None
:type port: Optional[str], optional
:param batch_size: Number of items to insert in one batch, defaults to 100
:type batch_size: Optional[int], optional
:param allow_reset: Resets the database. defaults to False
:type allow_reset: bool
:param chroma_settings: Chroma settings dict, defaults to None
@@ -34,4 +37,5 @@ class ChromaDbConfig(BaseVectorDbConfig):
self.chroma_settings = chroma_settings
self.allow_reset = allow_reset
self.batch_size = batch_size
super().__init__(collection_name=collection_name, dir=dir, host=host, port=port)
@@ -13,6 +13,7 @@ class ElasticsearchDBConfig(BaseVectorDbConfig):
dir: Optional[str] = None,
es_url: Union[str, list[str]] = None,
cloud_id: Optional[str] = None,
batch_size: Optional[int] = 100,
**ES_EXTRA_PARAMS: dict[str, any],
):
"""
@@ -24,6 +25,10 @@ class ElasticsearchDBConfig(BaseVectorDbConfig):
:type dir: Optional[str], optional
:param es_url: elasticsearch url or list of nodes url to be used for connection, defaults to None
:type es_url: Union[str, list[str]], optional
:param cloud_id: cloud id of the elasticsearch cluster, defaults to None
:type cloud_id: Optional[str], optional
:param batch_size: Number of items to insert in one batch, defaults to 100
:type batch_size: Optional[int], optional
:param ES_EXTRA_PARAMS: extra params dict that can be passed to elasticsearch.
:type ES_EXTRA_PARAMS: dict[str, Any], optional
"""
@@ -46,4 +51,6 @@ class ElasticsearchDBConfig(BaseVectorDbConfig):
and not self.ES_EXTRA_PARAMS.get("bearer_auth")
):
self.ES_EXTRA_PARAMS["api_key"] = os.environ.get("ELASTICSEARCH_API_KEY")
self.batch_size = batch_size
super().__init__(collection_name=collection_name, dir=dir)
+33
View File
@@ -0,0 +1,33 @@
from typing import Optional
from embedchain.config.vectordb.base import BaseVectorDbConfig
from embedchain.helpers.json_serializable import register_deserializable
@register_deserializable
class LanceDBConfig(BaseVectorDbConfig):
def __init__(
self,
collection_name: Optional[str] = None,
dir: Optional[str] = None,
host: Optional[str] = None,
port: Optional[str] = None,
allow_reset=True,
):
"""
Initializes a configuration class instance for LanceDB.
:param collection_name: Default name for the collection, defaults to None
:type collection_name: Optional[str], optional
:param dir: Path to the database directory, where the database is stored, defaults to None
:type dir: Optional[str], optional
:param host: Database connection remote host. Use this if you run Embedchain as a client, defaults to None
:type host: Optional[str], optional
:param port: Database connection remote port. Use this if you run Embedchain as a client, defaults to None
:type port: Optional[str], optional
:param allow_reset: Resets the database. defaults to False
:type allow_reset: bool
"""
self.allow_reset = allow_reset
super().__init__(collection_name=collection_name, dir=dir, host=host, port=port)
+4
View File
@@ -13,6 +13,7 @@ class OpenSearchDBConfig(BaseVectorDbConfig):
vector_dimension: int = 1536,
collection_name: Optional[str] = None,
dir: Optional[str] = None,
batch_size: Optional[int] = 100,
**extra_params: dict[str, any],
):
"""
@@ -28,10 +29,13 @@ class OpenSearchDBConfig(BaseVectorDbConfig):
: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
:param batch_size: Number of items to insert in one batch, defaults to 100
:type batch_size: Optional[int], optional
"""
self.opensearch_url = opensearch_url
self.http_auth = http_auth
self.vector_dimension = vector_dimension
self.extra_params = extra_params
self.batch_size = batch_size
super().__init__(collection_name=collection_name, dir=dir)
+2
View File
@@ -17,6 +17,7 @@ class PineconeDBConfig(BaseVectorDbConfig):
serverless_config: Optional[dict[str, any]] = None,
hybrid_search: bool = False,
bm25_encoder: any = None,
batch_size: Optional[int] = 100,
**extra_params: dict[str, any],
):
self.metric = metric
@@ -26,6 +27,7 @@ class PineconeDBConfig(BaseVectorDbConfig):
self.extra_params = extra_params
self.hybrid_search = hybrid_search
self.bm25_encoder = bm25_encoder
self.batch_size = batch_size
if pod_config is None and serverless_config is None:
# If no config is provided, use the default pod spec config
pod_environment = os.environ.get("PINECONE_ENV", "gcp-starter")
+4
View File
@@ -18,6 +18,7 @@ class QdrantDBConfig(BaseVectorDbConfig):
hnsw_config: Optional[dict[str, any]] = None,
quantization_config: Optional[dict[str, any]] = None,
on_disk: Optional[bool] = None,
batch_size: Optional[int] = 10,
**extra_params: dict[str, any],
):
"""
@@ -36,9 +37,12 @@ class QdrantDBConfig(BaseVectorDbConfig):
This setting saves RAM by (slightly) increasing the response time.
Note: those payload values that are involved in filtering and are indexed - remain in RAM.
:type on_disk: bool, optional, defaults to None
:param batch_size: Number of items to insert in one batch, defaults to 10
:type batch_size: Optional[int], optional
"""
self.hnsw_config = hnsw_config
self.quantization_config = quantization_config
self.on_disk = on_disk
self.batch_size = batch_size
self.extra_params = extra_params
super().__init__(collection_name=collection_name, dir=dir)
+2
View File
@@ -10,7 +10,9 @@ class WeaviateDBConfig(BaseVectorDbConfig):
self,
collection_name: Optional[str] = None,
dir: Optional[str] = None,
batch_size: Optional[int] = 100,
**extra_params: dict[str, any],
):
self.batch_size = batch_size
self.extra_params = extra_params
super().__init__(collection_name=collection_name, dir=dir)
+1 -1
View File
@@ -2,7 +2,7 @@ import os
from pathlib import Path
ABS_PATH = os.getcwd()
HOME_DIR = str(Path.home())
HOME_DIR = os.environ.get("EMBEDCHAIN_CONFIG_DIR", str(Path.home()))
CONFIG_DIR = os.path.join(HOME_DIR, ".embedchain")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
SQLITE_PATH = os.path.join(CONFIG_DIR, "embedchain.db")
@@ -80,6 +80,8 @@ class DataFormatter(JSONSerializable):
DataType.SLACK: "embedchain.loaders.slack.SlackLoader",
DataType.DROPBOX: "embedchain.loaders.dropbox.DropboxLoader",
DataType.TEXT_FILE: "embedchain.loaders.text_file.TextFileLoader",
DataType.EXCEL_FILE: "embedchain.loaders.excel_file.ExcelFileLoader",
DataType.AUDIO: "embedchain.loaders.audio.AudioLoader",
}
if data_type == DataType.CUSTOM or loader is not None:
@@ -127,6 +129,8 @@ class DataFormatter(JSONSerializable):
DataType.SLACK: "embedchain.chunkers.common_chunker.CommonChunker",
DataType.DROPBOX: "embedchain.chunkers.common_chunker.CommonChunker",
DataType.TEXT_FILE: "embedchain.chunkers.common_chunker.CommonChunker",
DataType.EXCEL_FILE: "embedchain.chunkers.excel_file.ExcelFileChunker",
DataType.AUDIO: "embedchain.chunkers.audio.AudioChunker",
}
if chunker is not None:
+103 -34
View File
@@ -6,9 +6,7 @@ from typing import Any, Optional, Union
from dotenv import load_dotenv
from langchain.docstore.document import Document
from embedchain.cache import (adapt, get_gptcache_session,
gptcache_data_convert,
gptcache_update_cache_callback)
from embedchain.cache import adapt, get_gptcache_session, gptcache_data_convert, gptcache_update_cache_callback
from embedchain.chunkers.base_chunker import BaseChunker
from embedchain.config import AddConfig, BaseLlmConfig, ChunkerConfig
from embedchain.config.base_app_config import BaseAppConfig
@@ -18,8 +16,7 @@ from embedchain.embedder.base import BaseEmbedder
from embedchain.helpers.json_serializable import JSONSerializable
from embedchain.llm.base import BaseLlm
from embedchain.loaders.base_loader import BaseLoader
from embedchain.models.data_type import (DataType, DirectDataType,
IndirectDataType, SpecialDataType)
from embedchain.models.data_type import DataType, DirectDataType, IndirectDataType, SpecialDataType
from embedchain.utils.misc import detect_datatype, is_valid_json_string
from embedchain.vectordb.base import BaseVectorDB
@@ -55,6 +52,8 @@ class EmbedChain(JSONSerializable):
"""
self.config = config
self.cache_config = None
self.memory_config = None
self.mem0_client = None
# Llm
self.llm = llm
# Database has support for config assignment for backwards compatibility
@@ -97,13 +96,13 @@ class EmbedChain(JSONSerializable):
@property
def online(self):
return self.llm.online
return self.llm.config.online
@online.setter
def online(self, value):
if not isinstance(value, bool):
raise ValueError(f"Boolean value expected but got {type(value)}.")
self.llm.online = value
self.llm.config.online = value
def add(
self,
@@ -132,7 +131,14 @@ class EmbedChain(JSONSerializable):
:type config: Optional[AddConfig], optional
:raises ValueError: Invalid data type
:param dry_run: Optional. A dry run displays the chunks to ensure that the loader and chunker work as intended.
deafaults to False
defaults to False
:type dry_run: bool
:param loader: The loader to use to load the data, defaults to None
:type loader: BaseLoader, optional
:param chunker: The chunker to use to chunk the data, defaults to None
:type chunker: BaseChunker, optional
:param kwargs: To read more params for the query function
:type kwargs: dict[str, Any]
:return: source_hash, a md5-hash of the source, in hexadecimal representation.
:rtype: str
"""
@@ -179,6 +185,10 @@ class EmbedChain(JSONSerializable):
if data_type in {DataType.DOCS_SITE}:
self.is_docs_site_instance = True
# Convert the source to a string if it is not already
if not isinstance(source, str):
source = str(source)
# Insert the data into the 'ec_data_sources' table
self.db_session.add(
DataSource(
@@ -289,12 +299,19 @@ class EmbedChain(JSONSerializable):
Loads the data from the given URL, chunks it, and adds it to database.
:param loader: The loader to use to load the data.
:type loader: BaseLoader
:param chunker: The chunker to use to chunk the data.
:type chunker: BaseChunker
:param src: The data to be handled by the loader. Can be a URL for
remote sources or local content for local loaders.
:param metadata: Optional. Metadata associated with the data source.
:type src: Any
:param metadata: Metadata associated with the data source.
:type metadata: dict[str, Any], optional
:param source_hash: Hexadecimal hash of the source.
:param dry_run: Optional. A dry run returns chunks and doesn't update DB.
:type source_hash: str, optional
:param add_config: The `AddConfig` instance to use as configuration options.
:type add_config: AddConfig, optional
:param dry_run: A dry run returns chunks and doesn't update DB.
:type dry_run: bool, defaults to False
:return: (list) documents (embedded text), (list) metadata, (list) ids, (int) number of chunks
"""
@@ -310,12 +327,12 @@ class EmbedChain(JSONSerializable):
new_doc_id = embeddings_data["doc_id"]
if existing_doc_id and existing_doc_id == new_doc_id:
print("Doc content has not changed. Skipping creating chunks and embeddings")
logger.info("Doc content has not changed. Skipping creating chunks and embeddings")
return [], [], [], 0
# this means that doc content has changed.
if existing_doc_id and existing_doc_id != new_doc_id:
print("Doc content has changed. Recomputing chunks and embeddings intelligently.")
logger.info("Doc content has changed. Recomputing chunks and embeddings intelligently.")
self.db.delete({"doc_id": existing_doc_id})
# get existing ids, and discard doc if any common id exist.
@@ -341,7 +358,7 @@ class EmbedChain(JSONSerializable):
src_copy = src
if len(src_copy) > 50:
src_copy = src[:50] + "..."
print(f"All data from {src_copy} already exists in the database.")
logger.info(f"All data from {src_copy} already exists in the database.")
# Make sure to return a matching return type
return [], [], [], 0
@@ -388,12 +405,12 @@ class EmbedChain(JSONSerializable):
if batch_docs:
self.db.add(documents=batch_docs, metadatas=batch_meta, ids=batch_ids, **kwargs)
except Exception as e:
print(f"Failed to add batch due to a bad request: {e}")
logger.info(f"Failed to add batch due to a bad request: {e}")
# Handle the error, e.g., by logging, retrying, or skipping
pass
count_new_chunks = self.db.count() - chunks_before_addition
print(f"Successfully saved {src} ({chunker.data_type}). New chunks count: {count_new_chunks}")
logger.info(f"Successfully saved {str(src)[:100]} ({chunker.data_type}). New chunks count: {count_new_chunks}")
return list(documents), metadatas, ids, count_new_chunks
@@ -460,7 +477,7 @@ class EmbedChain(JSONSerializable):
where: Optional[dict] = None,
citations: bool = False,
**kwargs: dict[str, Any],
) -> Union[tuple[str, list[tuple[str, dict]]], str]:
) -> Union[tuple[str, list[tuple[str, dict]]], str, dict[str, Any]]:
"""
Queries the vector database based on the given input query.
Gets relevant doc based on the query and then passes it to an
@@ -470,18 +487,22 @@ class EmbedChain(JSONSerializable):
:type input_query: str
:param config: The `BaseLlmConfig` instance to use as configuration options. This is used for one method call.
To persistently use a config, declare it during app init., defaults to None
:type config: Optional[BaseLlmConfig], optional
:type config: BaseLlmConfig, optional
:param dry_run: A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response., defaults to False
:type dry_run: bool, optional
:param where: A dictionary of key-value pairs to filter the database results., defaults to None
:type where: Optional[dict[str, str]], optional
:type where: dict[str, str], optional
:param citations: A boolean to indicate if db should fetch citation source
:type citations: bool
:param kwargs: To read more params for the query function. Ex. we use citations boolean
param to return context along with the answer
:type kwargs: dict[str, Any]
:return: The answer to the query, with citations if the citation flag is True
or the dry run result
:rtype: str, if citations is False, otherwise tuple[str, list[tuple[str,str,str]]]
:rtype: str, if citations is False and token_usage is False, otherwise if citations is true then
tuple[str, list[tuple[str,str,str]]] and if token_usage is true then
tuple[str, list[tuple[str,str,str]], dict[str, Any]]
"""
contexts = self._retrieve_from_database(
input_query=input_query, config=config, where=where, citations=citations, **kwargs
@@ -504,17 +525,29 @@ class EmbedChain(JSONSerializable):
dry_run=dry_run,
)
else:
answer = self.llm.query(
input_query=input_query, contexts=contexts_data_for_llm_query, config=config, dry_run=dry_run
)
if self.llm.config.token_usage:
answer, token_info = self.llm.query(
input_query=input_query, contexts=contexts_data_for_llm_query, config=config, dry_run=dry_run
)
else:
answer = self.llm.query(
input_query=input_query, contexts=contexts_data_for_llm_query, config=config, dry_run=dry_run
)
# Send anonymous telemetry
self.telemetry.capture(event_name="query", properties=self._telemetry_props)
if citations:
if self.llm.config.token_usage:
return {"answer": answer, "contexts": contexts, "usage": token_info}
return answer, contexts
else:
return answer
if self.llm.config.token_usage:
return {"answer": answer, "usage": token_info}
logger.warning(
"Starting from v0.1.125 the return type of query method will be changed to tuple containing `answer`."
)
return answer
def chat(
self,
@@ -525,7 +558,7 @@ class EmbedChain(JSONSerializable):
where: Optional[dict[str, str]] = None,
citations: bool = False,
**kwargs: dict[str, Any],
) -> Union[tuple[str, list[tuple[str, dict]]], str]:
) -> Union[tuple[str, list[tuple[str, dict]]], str, dict[str, Any]]:
"""
Queries the vector database on the given input query.
Gets relevant doc based on the query and then passes it to an
@@ -537,20 +570,24 @@ class EmbedChain(JSONSerializable):
:type input_query: str
:param config: The `BaseLlmConfig` instance to use as configuration options. This is used for one method call.
To persistently use a config, declare it during app init., defaults to None
:type config: Optional[BaseLlmConfig], optional
:type config: BaseLlmConfig, optional
:param dry_run: A dry run does everything except send the resulting prompt to
the LLM. The purpose is to test the prompt, not the response., defaults to False
:type dry_run: bool, optional
:param session_id: The session id to use for chat history, defaults to 'default'.
:type session_id: Optional[str], optional
:type session_id: str, optional
:param where: A dictionary of key-value pairs to filter the database results., defaults to None
:type where: Optional[dict[str, str]], optional
:type where: dict[str, str], optional
:param citations: A boolean to indicate if db should fetch citation source
:type citations: bool
:param kwargs: To read more params for the query function. Ex. we use citations boolean
param to return context along with the answer
:type kwargs: dict[str, Any]
:return: The answer to the query, with citations if the citation flag is True
or the dry run result
:rtype: str, if citations is False, otherwise tuple[str, list[tuple[str,str,str]]]
:rtype: str, if citations is False and token_usage is False, otherwise if citations is true then
tuple[str, list[tuple[str,str,str]]] and if token_usage is true then
tuple[str, list[tuple[str,str,str]], dict[str, Any]]
"""
contexts = self._retrieve_from_database(
input_query=input_query, config=config, where=where, citations=citations, **kwargs
@@ -560,6 +597,12 @@ class EmbedChain(JSONSerializable):
else:
contexts_data_for_llm_query = contexts
memories = None
if self.mem0_client:
memories = self.mem0_client.search(
query=input_query, agent_id=self.config.id, session_id=session_id, limit=self.memory_config.top_k
)
# Update the history beforehand so that we can handle multiple chat sessions in the same python session
self.llm.update_history(app_id=self.config.id, session_id=session_id)
@@ -578,9 +621,28 @@ class EmbedChain(JSONSerializable):
)
else:
logger.debug("Cache disabled. Running chat without cache.")
answer = self.llm.chat(
input_query=input_query, contexts=contexts_data_for_llm_query, config=config, dry_run=dry_run
)
if self.llm.config.token_usage:
answer, token_info = self.llm.query(
input_query=input_query,
contexts=contexts_data_for_llm_query,
config=config,
dry_run=dry_run,
memories=memories,
)
else:
answer = self.llm.query(
input_query=input_query,
contexts=contexts_data_for_llm_query,
config=config,
dry_run=dry_run,
memories=memories,
)
# Add to Mem0 memory if enabled
# TODO: Might need to prepend with some text like:
# "Remember user preferences from following user query: {input_query}"
if self.mem0_client:
self.mem0_client.add(data=input_query, agent_id=self.config.id, session_id=session_id)
# add conversation in memory
self.llm.add_history(self.config.id, input_query, answer, session_id=session_id)
@@ -589,9 +651,16 @@ class EmbedChain(JSONSerializable):
self.telemetry.capture(event_name="chat", properties=self._telemetry_props)
if citations:
if self.llm.config.token_usage:
return {"answer": answer, "contexts": contexts, "usage": token_info}
return answer, contexts
else:
return answer
if self.llm.config.token_usage:
return {"answer": answer, "usage": token_info}
logger.warning(
"Starting from v0.1.125 the return type of query method will be changed to tuple containing `answer`."
)
return answer
def search(self, query, num_documents=3, where=None, raw_filter=None, namespace=None):
"""
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
from langchain_community.embeddings import AzureOpenAIEmbeddings
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base import BaseEmbedder
from embedchain.models import VectorDimensions
class AzureOpenAIEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
if self.config.model is None:
self.config.model = "text-embedding-ada-002"
embeddings = AzureOpenAIEmbeddings(deployment=self.config.deployment_name)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
self.set_embedding_fn(embedding_fn=embedding_fn)
vector_dimension = self.config.vector_dimension or VectorDimensions.OPENAI.value
self.set_vector_dimension(vector_dimension=vector_dimension)
+52
View File
@@ -0,0 +1,52 @@
import os
from typing import Optional, Union
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base import BaseEmbedder
from chromadb import EmbeddingFunction, Embeddings
class ClarifaiEmbeddingFunction(EmbeddingFunction):
def __init__(self, config: BaseEmbedderConfig) -> None:
super().__init__()
try:
from clarifai.client.model import Model
from clarifai.client.input import Inputs
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for ClarifaiEmbeddingFunction are not installed."
'Please install with `pip install --upgrade "embedchain[clarifai]"`'
) from None
self.config = config
self.api_key = config.api_key or os.getenv("CLARIFAI_PAT")
self.model = config.model
self.model_obj = Model(url=self.model, pat=self.api_key)
self.input_obj = Inputs(pat=self.api_key)
def __call__(self, input: Union[str, list[str]]) -> Embeddings:
if isinstance(input, str):
input = [input]
batch_size = 32
embeddings = []
try:
for i in range(0, len(input), batch_size):
batch = input[i : i + batch_size]
input_batch = [
self.input_obj.get_text_input(input_id=str(id), raw_text=inp) for id, inp in enumerate(batch)
]
response = self.model_obj.predict(input_batch)
embeddings.extend([list(output.data.embeddings[0].vector) for output in response.outputs])
except Exception as e:
print(f"Predict failed, exception: {e}")
return embeddings
class ClarifaiEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config)
embedding_func = ClarifaiEmbeddingFunction(config=self.config)
self.set_embedding_fn(embedding_fn=embedding_func)
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Optional
from langchain_community.embeddings import CohereEmbeddings
from langchain_cohere.embeddings import CohereEmbeddings
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base import BaseEmbedder
+3 -3
View File
@@ -9,10 +9,10 @@ class GPT4AllEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
from langchain.embeddings import \
GPT4AllEmbeddings as LangchainGPT4AllEmbeddings
from langchain_community.embeddings import GPT4AllEmbeddings as LangchainGPT4AllEmbeddings
embeddings = LangchainGPT4AllEmbeddings()
model_name = self.config.model or "all-MiniLM-L6-v2-f16.gguf"
embeddings = LangchainGPT4AllEmbeddings(model_name=model_name)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
self.set_embedding_fn(embedding_fn=embedding_fn)
+22 -1
View File
@@ -1,7 +1,16 @@
import os
from typing import Optional
from langchain_community.embeddings import HuggingFaceEmbeddings
try:
from langchain_huggingface import HuggingFaceEndpointEmbeddings
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for HuggingFaceHub are not installed."
"Please install with `pip install langchain_huggingface`"
) from None
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base import BaseEmbedder
from embedchain.models import VectorDimensions
@@ -11,7 +20,19 @@ class HuggingFaceEmbedder(BaseEmbedder):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config=config)
embeddings = HuggingFaceEmbeddings(model_name=self.config.model)
if self.config.endpoint:
if not self.config.api_key and "HUGGINGFACE_ACCESS_TOKEN" not in os.environ:
raise ValueError(
"Please set the HUGGINGFACE_ACCESS_TOKEN environment variable or pass API Key in the config."
)
embeddings = HuggingFaceEndpointEmbeddings(
model=self.config.endpoint,
huggingfacehub_api_token=self.config.api_key or os.getenv("HUGGINGFACE_ACCESS_TOKEN"),
)
else:
embeddings = HuggingFaceEmbeddings(model_name=self.config.model, model_kwargs=self.config.model_kwargs)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
self.set_embedding_fn(embedding_fn=embedding_fn)
+32
View File
@@ -0,0 +1,32 @@
import logging
from typing import Optional
try:
from ollama import Client
except ImportError:
raise ImportError("Ollama Embedder requires extra dependencies. Install with `pip install ollama`") from None
from langchain_community.embeddings import OllamaEmbeddings
from embedchain.config import OllamaEmbedderConfig
from embedchain.embedder.base import BaseEmbedder
from embedchain.models import VectorDimensions
logger = logging.getLogger(__name__)
class OllamaEmbedder(BaseEmbedder):
def __init__(self, config: Optional[OllamaEmbedderConfig] = None):
super().__init__(config=config)
client = Client(host=config.base_url)
local_models = client.list()["models"]
if not any(model.get("name") == self.config.model for model in local_models):
logger.info(f"Pulling {self.config.model} from Ollama!")
client.pull(self.config.model)
embeddings = OllamaEmbeddings(model=self.config.model, base_url=config.base_url)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
self.set_embedding_fn(embedding_fn=embedding_fn)
vector_dimension = self.config.vector_dimension or VectorDimensions.OLLAMA.value
self.set_vector_dimension(vector_dimension=vector_dimension)
+9 -15
View File
@@ -2,7 +2,7 @@ import os
from typing import Optional
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
from langchain_community.embeddings import AzureOpenAIEmbeddings
from embedchain.config import BaseEmbedderConfig
from embedchain.embedder.base import BaseEmbedder
@@ -19,20 +19,14 @@ class OpenAIEmbedder(BaseEmbedder):
api_key = self.config.api_key or os.environ["OPENAI_API_KEY"]
api_base = self.config.api_base or os.environ.get("OPENAI_API_BASE")
if self.config.deployment_name:
embeddings = AzureOpenAIEmbeddings(deployment=self.config.deployment_name)
embedding_fn = BaseEmbedder._langchain_default_concept(embeddings)
else:
if api_key is None and os.getenv("OPENAI_ORGANIZATION") is None:
raise ValueError(
"OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided"
) # noqa:E501
embedding_fn = OpenAIEmbeddingFunction(
api_key=api_key,
api_base=api_base,
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name=self.config.model,
)
if api_key is None and os.getenv("OPENAI_ORGANIZATION") is None:
raise ValueError("OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided") # noqa:E501
embedding_fn = OpenAIEmbeddingFunction(
api_key=api_key,
api_base=api_base,
organization_id=os.getenv("OPENAI_ORGANIZATION"),
model_name=self.config.model,
)
self.set_embedding_fn(embedding_fn=embedding_fn)
vector_dimension = self.config.vector_dimension or VectorDimensions.OPENAI.value
self.set_vector_dimension(vector_dimension=vector_dimension)
+9 -1
View File
@@ -23,8 +23,10 @@ class LlmFactory:
"google": "embedchain.llm.google.GoogleLlm",
"aws_bedrock": "embedchain.llm.aws_bedrock.AWSBedrockLlm",
"mistralai": "embedchain.llm.mistralai.MistralAILlm",
"clarifai": "embedchain.llm.clarifai.ClarifaiLlm",
"groq": "embedchain.llm.groq.GroqLlm",
"nvidia": "embedchain.llm.nvidia.NvidiaLlm",
"vllm": "embedchain.llm.vllm.VLLM",
}
provider_to_config_class = {
"embedchain": "embedchain.config.llm.base.BaseLlmConfig",
@@ -48,22 +50,26 @@ class LlmFactory:
class EmbedderFactory:
provider_to_class = {
"azure_openai": "embedchain.embedder.openai.OpenAIEmbedder",
"azure_openai": "embedchain.embedder.azure_openai.AzureOpenAIEmbedder",
"gpt4all": "embedchain.embedder.gpt4all.GPT4AllEmbedder",
"huggingface": "embedchain.embedder.huggingface.HuggingFaceEmbedder",
"openai": "embedchain.embedder.openai.OpenAIEmbedder",
"vertexai": "embedchain.embedder.vertexai.VertexAIEmbedder",
"google": "embedchain.embedder.google.GoogleAIEmbedder",
"mistralai": "embedchain.embedder.mistralai.MistralAIEmbedder",
"clarifai": "embedchain.embedder.clarifai.ClarifaiEmbedder",
"nvidia": "embedchain.embedder.nvidia.NvidiaEmbedder",
"cohere": "embedchain.embedder.cohere.CohereEmbedder",
"ollama": "embedchain.embedder.ollama.OllamaEmbedder",
}
provider_to_config_class = {
"azure_openai": "embedchain.config.embedder.base.BaseEmbedderConfig",
"google": "embedchain.config.embedder.google.GoogleAIEmbedderConfig",
"gpt4all": "embedchain.config.embedder.base.BaseEmbedderConfig",
"huggingface": "embedchain.config.embedder.base.BaseEmbedderConfig",
"clarifai": "embedchain.config.embedder.base.BaseEmbedderConfig",
"openai": "embedchain.config.embedder.base.BaseEmbedderConfig",
"ollama": "embedchain.config.embedder.ollama.OllamaEmbedderConfig",
}
@classmethod
@@ -85,6 +91,7 @@ class VectorDBFactory:
"chroma": "embedchain.vectordb.chroma.ChromaDB",
"elasticsearch": "embedchain.vectordb.elasticsearch.ElasticsearchDB",
"opensearch": "embedchain.vectordb.opensearch.OpenSearchDB",
"lancedb": "embedchain.vectordb.lancedb.LanceDB",
"pinecone": "embedchain.vectordb.pinecone.PineconeDB",
"qdrant": "embedchain.vectordb.qdrant.QdrantDB",
"weaviate": "embedchain.vectordb.weaviate.WeaviateDB",
@@ -94,6 +101,7 @@ class VectorDBFactory:
"chroma": "embedchain.config.vectordb.chroma.ChromaDbConfig",
"elasticsearch": "embedchain.config.vectordb.elasticsearch.ElasticsearchDBConfig",
"opensearch": "embedchain.config.vectordb.opensearch.OpenSearchDBConfig",
"lancedb": "embedchain.config.vectordb.lancedb.LanceDBConfig",
"pinecone": "embedchain.config.vectordb.pinecone.PineconeDBConfig",
"qdrant": "embedchain.config.vectordb.qdrant.QdrantDBConfig",
"weaviate": "embedchain.config.vectordb.weaviate.WeaviateDBConfig",
+4 -5
View File
@@ -97,10 +97,8 @@ class JSONSerializable:
dict: A dictionary representation of the object.
"""
if hasattr(obj, "__dict__"):
dct = obj.__dict__.copy()
for key, value in list(
dct.items()
): # We use list() to get a copy of items to avoid dictionary size change during iteration.
dct = {}
for key, value in obj.__dict__.items():
try:
# Recursive: If the value is an instance of a subclass of JSONSerializable,
# serialize it using the JSONSerializable serialize method.
@@ -120,8 +118,9 @@ class JSONSerializable:
# NOTE: Keep in mind that this logic needs to be applied to the decoder too.
else:
json.dumps(value) # Try to serialize the value.
dct[key] = value
except TypeError:
del dct[key] # If it fails, remove the key-value pair from the dictionary.
pass # If it fails, simply pass to skip this key-value pair of the dictionary.
dct["__class__"] = obj.__class__.__name__
return dct
+35 -11
View File
@@ -1,6 +1,11 @@
import logging
import os
from typing import Optional
from typing import Any, Optional
try:
from langchain_anthropic import ChatAnthropic
except ImportError:
raise ImportError("Please install the langchain-anthropic package by running `pip install langchain-anthropic`.")
from embedchain.config import BaseLlmConfig
from embedchain.helpers.json_serializable import register_deserializable
@@ -12,24 +17,43 @@ logger = logging.getLogger(__name__)
@register_deserializable
class AnthropicLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
if "ANTHROPIC_API_KEY" not in os.environ:
raise ValueError("Please set the ANTHROPIC_API_KEY environment variable.")
super().__init__(config=config)
if not self.config.api_key and "ANTHROPIC_API_KEY" not in os.environ:
raise ValueError("Please set the ANTHROPIC_API_KEY environment variable or pass it in the config.")
def get_llm_model_answer(self, prompt):
return AnthropicLlm._get_answer(prompt=prompt, config=self.config)
def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]:
if self.config.token_usage:
response, token_info = self._get_answer(prompt, self.config)
model_name = "anthropic/" + self.config.model
if model_name not in self.config.model_pricing_map:
raise ValueError(
f"Model {model_name} not found in `model_prices_and_context_window.json`. \
You can disable token usage by setting `token_usage` to False."
)
total_cost = (
self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["input_tokens"]
) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["output_tokens"]
response_token_info = {
"prompt_tokens": token_info["input_tokens"],
"completion_tokens": token_info["output_tokens"],
"total_tokens": token_info["input_tokens"] + token_info["output_tokens"],
"total_cost": round(total_cost, 10),
"cost_currency": "USD",
}
return response, response_token_info
return self._get_answer(prompt, self.config)
@staticmethod
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain_community.chat_models import ChatAnthropic
chat = ChatAnthropic(
anthropic_api_key=os.environ["ANTHROPIC_API_KEY"], temperature=config.temperature, model=config.model
)
api_key = config.api_key or os.getenv("ANTHROPIC_API_KEY")
chat = ChatAnthropic(anthropic_api_key=api_key, temperature=config.temperature, model_name=config.model)
if config.max_tokens and config.max_tokens != 1000:
logger.warning("Config option `max_tokens` is not supported by this model.")
messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
chat_response = chat.invoke(messages)
if config.token_usage:
return chat_response.content, chat_response.response_metadata["token_usage"]
return chat_response.content
+4 -4
View File
@@ -14,18 +14,18 @@ class AzureOpenAILlm(BaseLlm):
super().__init__(config=config)
def get_llm_model_answer(self, prompt):
return AzureOpenAILlm._get_answer(prompt=prompt, config=self.config)
return self._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
from langchain_community.chat_models import AzureChatOpenAI
from langchain_openai import AzureChatOpenAI
if not config.deployment_name:
raise ValueError("Deployment name must be provided for Azure OpenAI")
chat = AzureChatOpenAI(
deployment_name=config.deployment_name,
openai_api_version="2023-05-15",
openai_api_version=str(config.api_version) if config.api_version else "2024-02-01",
model_name=config.model or "gpt-3.5-turbo",
temperature=config.temperature,
max_tokens=config.max_tokens,
@@ -37,4 +37,4 @@ class AzureOpenAILlm(BaseLlm):
messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt)
return chat(messages).content
return chat.invoke(messages).content
+48 -16
View File
@@ -5,9 +5,12 @@ from typing import Any, Optional
from langchain.schema import BaseMessage as LCBaseMessage
from embedchain.config import BaseLlmConfig
from embedchain.config.llm.base import (DEFAULT_PROMPT,
DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE,
DOCS_SITE_PROMPT_TEMPLATE)
from embedchain.config.llm.base import (
DEFAULT_PROMPT,
DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE,
DEFAULT_PROMPT_WITH_MEM0_MEMORY_TEMPLATE,
DOCS_SITE_PROMPT_TEMPLATE,
)
from embedchain.helpers.json_serializable import JSONSerializable
from embedchain.memory.base import ChatHistory
from embedchain.memory.message import ChatMessage
@@ -29,7 +32,6 @@ class BaseLlm(JSONSerializable):
self.memory = ChatHistory()
self.is_docs_site_instance = False
self.online = False
self.history: Any = None
def get_llm_model_answer(self):
@@ -75,6 +77,16 @@ class BaseLlm(JSONSerializable):
"""
return "\n".join(self.history)
def _format_memories(self, memories: list[dict]) -> str:
"""Format memories to be used in prompt
:param memories: Memories to format
:type memories: list[dict]
:return: Formatted memories
:rtype: str
"""
return "\n".join([memory["text"] for memory in memories])
def generate_prompt(self, input_query: str, contexts: list[str], **kwargs: dict[str, Any]) -> str:
"""
Generates a prompt based on the given query and context, ready to be
@@ -89,6 +101,7 @@ class BaseLlm(JSONSerializable):
"""
context_string = " | ".join(contexts)
web_search_result = kwargs.get("web_search_result", "")
memories = kwargs.get("memories", None)
if web_search_result:
context_string = self._append_search_and_context(context_string, web_search_result)
@@ -104,10 +117,19 @@ class BaseLlm(JSONSerializable):
not self.config._validate_prompt_history(self.config.prompt)
and self.config.prompt.template == DEFAULT_PROMPT
):
# swap in the template with history
prompt = DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE.substitute(
context=context_string, query=input_query, history=self._format_history()
)
if memories:
# swap in the template with Mem0 memory template
prompt = DEFAULT_PROMPT_WITH_MEM0_MEMORY_TEMPLATE.substitute(
context=context_string,
query=input_query,
history=self._format_history(),
memories=self._format_memories(memories),
)
else:
# swap in the template with history
prompt = DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE.substitute(
context=context_string, query=input_query, history=self._format_history()
)
else:
# If we can't swap in the default, we still proceed but tell users that the history is ignored.
logger.warning(
@@ -165,7 +187,7 @@ class BaseLlm(JSONSerializable):
return search.run(input_query)
@staticmethod
def _stream_response(answer: Any) -> Generator[Any, Any, None]:
def _stream_response(answer: Any, token_info: Optional[dict[str, Any]] = None) -> Generator[Any, Any, None]:
"""Generator to be used as streaming response
:param answer: Answer chunk from llm
@@ -178,8 +200,10 @@ class BaseLlm(JSONSerializable):
streamed_answer = streamed_answer + chunk
yield chunk
logger.info(f"Answer: {streamed_answer}")
if token_info:
logger.info(f"Token Info: {token_info}")
def query(self, input_query: str, contexts: list[str], config: BaseLlmConfig = None, dry_run=False):
def query(self, input_query: str, contexts: list[str], config: BaseLlmConfig = None, dry_run=False, memories=None):
"""
Queries the vector database based on the given input query.
Gets relevant doc based on the query and then passes it to an
@@ -213,18 +237,26 @@ class BaseLlm(JSONSerializable):
self.config.prompt = DOCS_SITE_PROMPT_TEMPLATE
self.config.number_documents = 5
k = {}
if self.online:
if self.config.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
k["memories"] = memories
prompt = self.generate_prompt(input_query, contexts, **k)
logger.info(f"Prompt: {prompt}")
if dry_run:
return prompt
answer = self.get_answer_from_llm(prompt)
if self.config.token_usage:
answer, token_info = self.get_answer_from_llm(prompt)
else:
answer = self.get_answer_from_llm(prompt)
if isinstance(answer, str):
logger.info(f"Answer: {answer}")
if self.config.token_usage:
return answer, token_info
return answer
else:
if self.config.token_usage:
return self._stream_response(answer, token_info)
return self._stream_response(answer)
finally:
if config:
@@ -268,7 +300,7 @@ class BaseLlm(JSONSerializable):
self.config.prompt = DOCS_SITE_PROMPT_TEMPLATE
self.config.number_documents = 5
k = {}
if self.online:
if self.config.online:
k["web_search_result"] = self.access_search_and_get_results(input_query)
prompt = self.generate_prompt(input_query, contexts, **k)
@@ -277,13 +309,13 @@ class BaseLlm(JSONSerializable):
if dry_run:
return prompt
answer = self.get_answer_from_llm(prompt)
answer, token_info = self.get_answer_from_llm(prompt)
if isinstance(answer, str):
logger.info(f"Answer: {answer}")
return answer
return answer, token_info
else:
# this is a streamed response and needs to be handled differently.
return self._stream_response(answer)
return self._stream_response(answer, token_info)
finally:
if config:
# Restore previous config
+47
View File
@@ -0,0 +1,47 @@
import logging
import os
from typing import Optional
from embedchain.config import BaseLlmConfig
from embedchain.helpers.json_serializable import register_deserializable
from embedchain.llm.base import BaseLlm
@register_deserializable
class ClarifaiLlm(BaseLlm):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config=config)
if not self.config.api_key and "CLARIFAI_PAT" not in os.environ:
raise ValueError("Please set the CLARIFAI_PAT environment variable.")
def get_llm_model_answer(self, prompt):
return self._get_answer(prompt=prompt, config=self.config)
@staticmethod
def _get_answer(prompt: str, config: BaseLlmConfig) -> str:
try:
from clarifai.client.model import Model
except ModuleNotFoundError:
raise ModuleNotFoundError(
"The required dependencies for Clarifai are not installed."
'Please install with `pip install --upgrade "embedchain[clarifai]"`'
) from None
model_name = config.model
logging.info(f"Using clarifai LLM model: {model_name}")
api_key = config.api_key or os.getenv("CLARIFAI_PAT")
model = Model(url=model_name, pat=api_key)
params = config.model_kwargs
try:
(params := {}) if config.model_kwargs is None else config.model_kwargs
predict_response = model.predict_by_bytes(
bytes(prompt, "utf-8"),
input_type="text",
inference_params=params,
)
text = predict_response.outputs[0].data.text.raw
return text
except Exception as e:
logging.error(f"Predict failed, exception: {e}")

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