Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 564036a166 | |||
| abf99ce5ea | |||
| f0f5c34acb | |||
| ed319531bf | |||
| d8d0e0e5d1 | |||
| 27c91bbd2d | |||
| f76d9740c6 | |||
| f29443a0fc | |||
| 35b022d6bc | |||
| 0dd1faf57f | |||
| b57f096b27 | |||
| 4c8876f032 | |||
| f92e890aa1 | |||
| 849de5e8ab | |||
| 7585bc557b | |||
| 4021d93168 | |||
| 28e06be26f | |||
| 39861ec1e8 | |||
| e3ae84b80d |
@@ -1 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Discord
|
||||
url: https://discord.gg/6PzXDgEjG5
|
||||
about: General community discussions
|
||||
@@ -0,0 +1,11 @@
|
||||
name: Documentation
|
||||
description: Report an issue related to the Embedchain docs.
|
||||
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Issue with current documentation:"
|
||||
description: >
|
||||
Please make sure to leave a reference to the document/code you're
|
||||
referring to.
|
||||
@@ -1,8 +1,9 @@
|
||||
name: 🚀 Feature request
|
||||
description: Submit a proposal/request for a new embedchain feature
|
||||
description: Submit a proposal/request for a new Embedchain feature
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature-request
|
||||
attributes:
|
||||
label: 🚀 The feature
|
||||
description: >
|
||||
@@ -16,16 +17,6 @@ body:
|
||||
Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: >
|
||||
A description of any alternative solutions or features you've considered, if any.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: >
|
||||
Add any other context or screenshots about the feature request.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
|
||||
+28
-16
@@ -1,24 +1,36 @@
|
||||
name: cd
|
||||
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
types: [published] # This will trigger the workflow when you create a new release
|
||||
|
||||
jobs:
|
||||
publish_to_pypi:
|
||||
name: publish to pypi on new release
|
||||
build-n-publish:
|
||||
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# IMPORTANT: this permission is mandatory for trusted publishing
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: JRubics/poetry-publish@v1.16
|
||||
name: Build and publish to PyPI
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_TOKEN }}
|
||||
ignore_dev_requirements: "yes"
|
||||
repository_url: https://upload.pypi.org/legacy/
|
||||
repository_name: embedchain
|
||||
python-version: 3.10
|
||||
|
||||
- name: Install pep517
|
||||
run: |
|
||||
python -m pip install pep517 --user
|
||||
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python -m pep517.build .
|
||||
|
||||
- name: Publish distribution 📦 to Test PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
||||
@@ -28,8 +28,8 @@ pip install embedchain
|
||||
zuck_bot = Llama2App()
|
||||
|
||||
# Embed your data
|
||||
zuck_bot.add("youtube_video", "https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
|
||||
zuck_bot.add("web_page", "https://en.wikipedia.org/wiki/Mark_Zuckerberg")
|
||||
zuck_bot.add("https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
|
||||
zuck_bot.add("https://en.wikipedia.org/wiki/Mark_Zuckerberg")
|
||||
|
||||
# Nice, your bot is ready now. Start asking questions to your bot.
|
||||
zuck_bot.query("Who is Mark Zuckerberg?")
|
||||
@@ -64,9 +64,9 @@ os.environ["OPENAI_API_KEY"] = "YOUR API KEY"
|
||||
elon_bot = App()
|
||||
|
||||
# Embed online resources
|
||||
elon_bot.add("web_page", "https://en.wikipedia.org/wiki/Elon_Musk")
|
||||
elon_bot.add("web_page", "https://tesla.com/elon-musk")
|
||||
elon_bot.add("youtube_video", "https://www.youtube.com/watch?v=MxZpaJK74Y4")
|
||||
elon_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
|
||||
elon_bot.add("https://tesla.com/elon-musk")
|
||||
elon_bot.add("https://www.youtube.com/watch?v=MxZpaJK74Y4")
|
||||
|
||||
# Query the bot
|
||||
elon_bot.query("How many companies does Elon Musk run?")
|
||||
@@ -78,6 +78,9 @@ elon_bot.query("How many companies does Elon Musk run?")
|
||||
Contributions are welcome! Please check out the issues on the repository, and feel free to open a pull request.
|
||||
For more information, please see the [contributing guidelines](CONTRIBUTING.md).
|
||||
|
||||
For more refrence, please go through [Development Guide](https://docs.embedchain.ai/contribution/dev) and [Documentation Guide](https://docs.embedchain.ai/contribution/docs).
|
||||
|
||||
|
||||
## Citation
|
||||
|
||||
If you utilize this repository, please consider citing it with:
|
||||
|
||||
@@ -6,20 +6,20 @@ title: '➕ Adding Data'
|
||||
|
||||
- This step assumes that you have already created an `app` instance by either using `App`, `OpenSourceApp` or `CustomApp`. We are calling our app instance as `naval_chat_bot` 🤖
|
||||
|
||||
- Now use `.add()` function to add any dataset.
|
||||
- Now use `.add` method to add any dataset.
|
||||
|
||||
```python
|
||||
# naval_chat_bot = App() or
|
||||
# naval_chat_bot = OpenSourceApp()
|
||||
|
||||
# Embed Online Resources
|
||||
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
|
||||
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
|
||||
naval_chat_bot.add("web_page", "https://nav.al/feedback")
|
||||
naval_chat_bot.add("web_page", "https://nav.al/agi")
|
||||
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
|
||||
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
|
||||
naval_chat_bot.add("https://nav.al/feedback")
|
||||
naval_chat_bot.add("https://nav.al/agi")
|
||||
|
||||
# Embed Local Resources
|
||||
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
|
||||
naval_chat_bot.add(("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
|
||||
```
|
||||
|
||||
The possible formats to add data can be found on the [Supported Data Formats](/advanced/data_types) page.
|
||||
|
||||
@@ -35,8 +35,8 @@ os.environ['REPLICATE_API_TOKEN'] = "REPLICATE API TOKEN"
|
||||
zuck_bot = Llama2App()
|
||||
|
||||
# Embed your data
|
||||
zuck_bot.add("youtube_video", "https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
|
||||
zuck_bot.add("web_page", "https://en.wikipedia.org/wiki/Mark_Zuckerberg")
|
||||
zuck_bot.add("https://www.youtube.com/watch?v=Ff4fRgnuFgQ")
|
||||
zuck_bot.add("https://en.wikipedia.org/wiki/Mark_Zuckerberg")
|
||||
|
||||
# Nice, your bot is ready now. Start asking questions to your bot.
|
||||
zuck_bot.query("Who is Mark Zuckerberg?")
|
||||
@@ -63,6 +63,7 @@ app = OpenSourceApp()
|
||||
- Here there is no need to setup any api keys. You just need to install embedchain package and these will get automatically installed. 📦
|
||||
- Once you have imported and instantiated the app, every functionality from here onwards is the same for either type of app. 📚
|
||||
- `OpenSourceApp` is opinionated. It uses the best open source embedding model and LLM on the market.
|
||||
- extra dependencies are required for this app type. Install them with `pip install embedchain[opensource]`.
|
||||
|
||||
### CustomApp
|
||||
|
||||
|
||||
@@ -26,17 +26,17 @@ naval_chat_bot = App(config)
|
||||
|
||||
# Example: define your own chunker config for `youtube_video`
|
||||
chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=100, length_function=len)
|
||||
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44", AddConfig(chunker=chunker_config))
|
||||
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44", AddConfig(chunker=chunker_config))
|
||||
|
||||
add_config = AddConfig()
|
||||
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf", add_config)
|
||||
naval_chat_bot.add("web_page", "https://nav.al/feedback", add_config)
|
||||
naval_chat_bot.add("web_page", "https://nav.al/agi", add_config)
|
||||
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf", config=add_config)
|
||||
naval_chat_bot.add("https://nav.al/feedback", config=add_config)
|
||||
naval_chat_bot.add("https://nav.al/agi", config=add_config)
|
||||
|
||||
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."), add_config)
|
||||
naval_chat_bot.add(("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."), config=add_config)
|
||||
|
||||
query_config = QueryConfig()
|
||||
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", query_config))
|
||||
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?", config=query_config))
|
||||
```
|
||||
|
||||
### Custom prompt template
|
||||
@@ -53,7 +53,7 @@ einstein_chat_bot = App()
|
||||
|
||||
# Embed Wikipedia page
|
||||
page = wikipedia.page("Albert Einstein")
|
||||
einstein_chat_bot.add("text", page.content)
|
||||
einstein_chat_bot.add(page.content)
|
||||
|
||||
# Example: use your own custom template with `$context` and `$query`
|
||||
einstein_chat_template = Template("""
|
||||
@@ -68,14 +68,14 @@ einstein_chat_template = Template("""
|
||||
|
||||
Human: $query
|
||||
Albert Einstein:""")
|
||||
query_config = QueryConfig(template=einstein_chat_template)
|
||||
query_config = QueryConfig(template=einstein_chat_template, system_prompt="You are Albert Einstein.")
|
||||
queries = [
|
||||
"Where did you complete your studies?",
|
||||
"Why did you win nobel prize?",
|
||||
"Why did you divorce your first wife?",
|
||||
]
|
||||
for query in queries:
|
||||
response = einstein_chat_bot.query(query, query_config)
|
||||
response = einstein_chat_bot.query(query, config=query_config)
|
||||
print("Query: ", query)
|
||||
print("Response: ", response)
|
||||
|
||||
|
||||
@@ -2,14 +2,40 @@
|
||||
title: '📋 Supported data formats'
|
||||
---
|
||||
|
||||
Embedchain supports following data formats:
|
||||
## Automatic data type detection
|
||||
The add method automatically tries to detect the data_type, based on your input for the source argument. So `app.add('https://www.youtube.com/watch?v=dQw4w9WgXcQ')` is enough to embed a YouTube video.
|
||||
|
||||
This detection is implemented for all formats. It is based on factors such as whether it's a URL, a local file, the source data type, etc.
|
||||
|
||||
### Debugging automatic detection
|
||||
|
||||
|
||||
Set `log_level=DEBUG` (in [AppConfig](http://localhost:3000/advanced/query_configuration#appconfig)) and make sure it's working as intended.
|
||||
|
||||
Otherwise, you will not know when, for instance, an invalid filepath is interpreted as raw text instead.
|
||||
|
||||
### Forcing a data type
|
||||
|
||||
To omit any issues with the data type detection, you can **force** a data_type by adding it as a `add` method argument.
|
||||
The examples below show you the keyword to force the respective `data_type`.
|
||||
|
||||
Forcing can also be used for edge cases, such as interpreting a sitemap as a web_page, for reading it's raw text instead of following links.
|
||||
|
||||
## Remote Data Types
|
||||
|
||||
<Tip>
|
||||
**Use local files in remote data types**
|
||||
|
||||
Some data_types are meant for remote content and only work with URLs.
|
||||
You can pass local files by formatting the path using the `file:` [URI scheme](https://en.wikipedia.org/wiki/File_URI_scheme), e.g. `file:///info.pdf`.
|
||||
</Tip>
|
||||
|
||||
### Youtube video
|
||||
|
||||
To add any youtube video to your app, use the data_type (first argument to `.add()` method) as `youtube_video`. Eg:
|
||||
|
||||
```python
|
||||
app.add('youtube_video', 'a_valid_youtube_url_here')
|
||||
app.add('a_valid_youtube_url_here', data_type='youtube_video')
|
||||
```
|
||||
|
||||
### PDF file
|
||||
@@ -17,7 +43,7 @@ app.add('youtube_video', 'a_valid_youtube_url_here')
|
||||
To add any pdf file, use the data_type as `pdf_file`. Eg:
|
||||
|
||||
```python
|
||||
app.add('pdf_file', 'a_valid_url_where_pdf_file_can_be_accessed')
|
||||
app.add('a_valid_url_where_pdf_file_can_be_accessed', data_type='pdf_file')
|
||||
```
|
||||
|
||||
Note that we do not support password protected pdfs.
|
||||
@@ -27,7 +53,7 @@ Note that we do not support password protected pdfs.
|
||||
To add any web page, use the data_type as `web_page`. Eg:
|
||||
|
||||
```python
|
||||
app.add('web_page', 'a_valid_web_page_url')
|
||||
app.add('a_valid_web_page_url', data_type='web_page')
|
||||
```
|
||||
|
||||
### Sitemap
|
||||
@@ -35,15 +61,16 @@ app.add('web_page', 'a_valid_web_page_url')
|
||||
Add all web pages from an xml-sitemap. Filters non-text files. Use the data_type as `sitemap`. Eg:
|
||||
|
||||
```python
|
||||
app.add('sitemap', 'https://example.com/sitemap.xml')
|
||||
app.add('https://example.com/sitemap.xml', data_type='sitemap')
|
||||
```
|
||||
|
||||
### Doc file
|
||||
|
||||
To add any doc/docx file, use the data_type as `docx`. Eg:
|
||||
To add any doc/docx file, use the data_type as `docx`. `docx` allows remote urls and conventional file paths. Eg:
|
||||
|
||||
```python
|
||||
app.add('docx', 'a_local_docx_file_path')
|
||||
app.add('https://example.com/content/intro.docx', data_type="docx")
|
||||
app.add('content/intro.docx', data_type="docx")
|
||||
```
|
||||
|
||||
### Code documentation website loader
|
||||
@@ -51,27 +78,29 @@ app.add('docx', 'a_local_docx_file_path')
|
||||
To add any code documentation website as a loader, use the data_type as `docs_site`. Eg:
|
||||
|
||||
```python
|
||||
app.add("docs_site", "https://docs.embedchain.ai/")
|
||||
app.add("https://docs.embedchain.ai/", data_type="docs_site")
|
||||
```
|
||||
|
||||
### Notion
|
||||
To use notion you must install the extra dependencies with `pip install embedchain[notion]`.
|
||||
|
||||
To load a notion page, use the data_type as `notion`.
|
||||
To load a notion page, use the data_type as `notion`. Since it is hard to automatically detect, forcing this is advised.
|
||||
The next argument must **end** with the `notion page id`. The id is a 32-character string. Eg:
|
||||
|
||||
```python
|
||||
app.add("notion", "cfbc134ca6464fc980d0391613959196")
|
||||
app.add("notion", "my-page-cfbc134ca6464fc980d0391613959196")
|
||||
app.add("notion", "https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196")
|
||||
app.add("cfbc134ca6464fc980d0391613959196", "notion")
|
||||
app.add("my-page-cfbc134ca6464fc980d0391613959196", "notion")
|
||||
app.add("https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196", "notion")
|
||||
```
|
||||
|
||||
## Local Data Types
|
||||
|
||||
### Text
|
||||
|
||||
To supply your own text, use the data_type as `text` and enter a string. The text is not processed, this can be very versatile. Eg:
|
||||
|
||||
```python
|
||||
app.add_local('text', 'Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.')
|
||||
app.add('Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.', data_type='text')
|
||||
```
|
||||
|
||||
Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit.
|
||||
@@ -81,7 +110,7 @@ Note: This is not used in the examples because in most cases you will supply a w
|
||||
To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg:
|
||||
|
||||
```python
|
||||
app.add_local('qna_pair', ("Question", "Answer"))
|
||||
app.add(("Question", "Answer"), data_type="qna_pair")
|
||||
```
|
||||
|
||||
## Reusing a vector database
|
||||
@@ -94,8 +123,8 @@ Create a local index:
|
||||
from embedchain import App
|
||||
|
||||
naval_chat_bot = App()
|
||||
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
|
||||
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
|
||||
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
|
||||
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
|
||||
```
|
||||
|
||||
You can reuse the local index with the same code, but without adding new documents:
|
||||
@@ -107,6 +136,6 @@ naval_chat_bot = App()
|
||||
print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?"))
|
||||
```
|
||||
|
||||
### More formats (coming soon!)
|
||||
## More formats (coming soon!)
|
||||
|
||||
- If you want to add any other format, please create an [issue](https://github.com/embedchain/embedchain/issues) and we will add it to the list of supported formats.
|
||||
- If you want to add any other format, please create an [issue](https://github.com/embedchain/embedchain/issues) and we will add it to the list of supported formats.
|
||||
@@ -25,7 +25,7 @@ Yes, you are passing `ChunkerConfig` to `AddConfig`, like so:
|
||||
```python
|
||||
chunker_config = ChunkerConfig(chunk_size=100)
|
||||
add_config = AddConfig(chunker=chunker_config)
|
||||
app.add_local("text", "lorem ipsum", config=add_config)
|
||||
app.add("lorem ipsum", config=add_config)
|
||||
```
|
||||
|
||||
### ChunkerConfig
|
||||
@@ -65,6 +65,8 @@ _coming soon_
|
||||
|top_p|Controls the diversity of words. Higher values (closer to 1) make word selection more diverse, lower values make words less diverse.|float|1|
|
||||
|history|include conversation history from your client or database.|any (recommendation: list[str])|None|
|
||||
|stream|control if response is streamed back to the user.|bool|False|
|
||||
|deployment_name|t.b.a.|str|None|
|
||||
|system_prompt|System prompt string. Unused if none.|str|None|
|
||||
|
||||
## ChatConfig
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: '🌍 API Server'
|
||||
---
|
||||
|
||||
The API Server based on Flask integrates the `embedchain` package, offering endpoints to add, query, and chat to engage in conversations with a chatbot using JSON requests.
|
||||
|
||||
### 🐳 Docker Setup
|
||||
|
||||
- Open variables.env, and edit it to add your 🔑 `OPENAI_API_KEY`.
|
||||
@@ -16,8 +18,8 @@ docker-compose up --build
|
||||
### 🚀 Usage Instructions
|
||||
|
||||
- Your api server is running on [http://localhost:5000/](http://localhost:5000/)
|
||||
- To use the api server, make an api call to the endpoints `/add` and `/query` using the json formats discussed below.
|
||||
- To add data sources to the bot:
|
||||
- To use the api server, make an api call to the endpoints `/add`, `/query` and `/chat` using the json formats discussed below.
|
||||
- To add data sources to the bot (/add):
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
@@ -30,7 +32,19 @@ docker-compose up --build
|
||||
"data": "Added data_type: url_or_text"
|
||||
}
|
||||
```
|
||||
- To ask questions from the bot:
|
||||
- To ask queries from the bot (/query):
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"question": "your_question_here"
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"data": "your_answer_here"
|
||||
}
|
||||
```
|
||||
- To chat with the bot (/chat):
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
@@ -43,4 +57,35 @@ docker-compose up --build
|
||||
}
|
||||
```
|
||||
|
||||
### 📡 Curl Call Formats
|
||||
|
||||
- To add data sources to the bot (/add):
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"data_type": "your_data_type_here",
|
||||
"url_or_text": "your_url_or_text_here"
|
||||
}' \
|
||||
http://localhost:5000/add
|
||||
```
|
||||
- To ask queries from the bot (/query):
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"question": "your_question_here"
|
||||
}' \
|
||||
http://localhost:5000/query
|
||||
```
|
||||
- To chat with the bot (/chat):
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"question": "your_question_here"
|
||||
}' \
|
||||
http://localhost:5000/chat
|
||||
```
|
||||
|
||||
🎉 Happy Chatting! 🎉
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: '💼 Slack Bot'
|
||||
---
|
||||
|
||||
### 🖼️ Template Setup
|
||||
|
||||
- Fork [this](https://replit.com/@taranjeetio/EC-Slack-Bot-Template?v=1#README.md) replit template.
|
||||
- Set your `OPENAI_API_KEY` in Secrets.
|
||||
- Create a workspace on Slack if you don't have one already by clicking [here](https://slack.com/intl/en-in/).
|
||||
- Create a new App on your Slack account by going [here](https://api.slack.com/apps).
|
||||
- Select `From Scratch`, then enter the Bot Name and select your workspace.
|
||||
- On the `Basic Information` page copy the `Signing Secret` and set it in your secrets as `SLACK_SIGNING_SECRET`.
|
||||
- On the left Sidebar, go to `OAuth and Permissions` and add the following scopes under `Bot Token Scopes`:
|
||||
```text
|
||||
app_mentions:read
|
||||
channels:history
|
||||
channels:read
|
||||
chat:write
|
||||
```
|
||||
- Now select the option `Install to Workspace` and after it's done, copy the `Bot User OAuth Token` and set it in your secrets as `SLACK_BOT_TOKEN`.
|
||||
- Start your replit container now by clicking on `Run`.
|
||||
- On the Slack API website go to `Event Subscriptions` on the left Sidebar and turn on `Enable Events`.
|
||||
- Copy the generated server URL in replit, append `/chat` at its end and paste it in `Request URL` box.
|
||||
- After it gets verified, click on `Subscribe to bot events`, add `message.channels` Bot User Event and click on `Save Changes`.
|
||||
- Now go to your workspace, click on the bot name in the Sidebar and then add the bot to any channel you want.
|
||||
|
||||
### 🚀 Usage Instructions
|
||||
|
||||
- Go to the channel where you have added your bot.
|
||||
- To add data sources to the bot, use the command:
|
||||
```text
|
||||
add <data_type> <url_or_text>
|
||||
```
|
||||
- To ask queries from the bot, use the command:
|
||||
```text
|
||||
query <question>
|
||||
```
|
||||
|
||||
🎉 Happy Chatting! 🎉
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: '📱 Telegram Bot'
|
||||
---
|
||||
|
||||
### 🖼️ Template Setup
|
||||
|
||||
- Fork [this](https://replit.com/@taranjeetio/EC-Telegram-Bot-Template?v=1#README.md) replit template.
|
||||
- Set your `OPENAI_API_KEY` in Secrets.
|
||||
- Open the Telegram app and search for the `BotFather` user.
|
||||
- Start a chat with BotFather and use the `/newbot` command to create a new bot.
|
||||
- Follow the instructions to choose a name and username for your bot.
|
||||
- Once the bot is created, BotFather will provide you with a unique token for your bot.
|
||||
- Set this token as `TELEGRAM_BOT_TOKEN` in Secrets.
|
||||
- Click on `Run` in the replit container and a URL will get generated for your bot.
|
||||
- Now set your webhook by running the following link in your browser:
|
||||
```url
|
||||
https://api.telegram.org/bot<Your_Telegram_Bot_Token>/setWebhook?url=<Replit_Generated_URL>
|
||||
```
|
||||
- When you get a successful response in your browser, your bot is ready to be used.
|
||||
|
||||
### 🚀 Usage Instructions
|
||||
|
||||
- Open your bot by searching for it using the bot name or bot username.
|
||||
- Click on `Start` or type `/start` and follow the on screen instructions.
|
||||
|
||||
🎉 Happy Chatting! 🎉
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: '💬 WhatsApp Bot'
|
||||
---
|
||||
|
||||
### 🚀 Getting started
|
||||
|
||||
1. Install embedchain python package:
|
||||
|
||||
```bash
|
||||
pip install embedchain
|
||||
```
|
||||
|
||||
2. Launch your WhatsApp bot:
|
||||
|
||||
|
||||
```bash
|
||||
python -m embedchain.bots.whatsapp --port 5000
|
||||
```
|
||||
|
||||
If your bot needs to be accessible online, use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible.
|
||||
|
||||
3. Create a free account on [Twilio](https://www.twilio.com/try-twilio)
|
||||
- Set up a WhatsApp Sandbox in your Twilio dashboard. Access it via the left sidebar: `Messaging > Try it out > Send a WhatsApp Message`.
|
||||
- Follow on-screen instructions to link a phone number for chatting with your bot
|
||||
- Copy your bot's public URL, add /chat at the end, and paste it in Twilio's WhatsApp Sandbox settings under "When a message comes in". Save the settings.
|
||||
|
||||
- Copy your bot's public url, append `/chat` at the end and paste it under `When a message comes in` under the `Sandbox settings` for Whatsapp in Twilio. Save your settings.
|
||||
|
||||
### 💬 How to use
|
||||
|
||||
- To connect a new number or reconnect an old one in the Sandbox, follow Twilio's instructions.
|
||||
- To include data sources, use this command:
|
||||
```text
|
||||
add <url_or_text>
|
||||
```
|
||||
|
||||
- To ask the bot questions, just type your query:
|
||||
```text
|
||||
<your-question-here>
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Here is an example of Elon Musk WhatsApp Bot that we created:
|
||||
|
||||
<img src="/images/whatsapp.jpg"/>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -7,7 +7,7 @@ description: '📝 Embedchain is a framework to easily create LLM powered bots o
|
||||
|
||||
Embedchain abstracts the entire process of loading a dataset, chunking it, creating embeddings, and storing it in a vector database.
|
||||
|
||||
You can add a single or multiple datasets using the .add and .add_local functions. Then, simply use the .query function to find answers from the added datasets.
|
||||
You can add a single or multiple datasets using the `.add` method. Then, simply use the `.query` method to find answers from the added datasets.
|
||||
|
||||
If you want to create a Naval Ravikant bot with a YouTube video, a book in PDF format, two blog posts, and a question and answer pair, all you need to do is add the respective links. Embedchain will take care of the rest, creating a bot for you.
|
||||
|
||||
@@ -16,13 +16,13 @@ from embedchain import App
|
||||
|
||||
naval_chat_bot = App()
|
||||
# Embed Online Resources
|
||||
naval_chat_bot.add("youtube_video", "https://www.youtube.com/watch?v=3qHkcs3kG44")
|
||||
naval_chat_bot.add("pdf_file", "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
|
||||
naval_chat_bot.add("web_page", "https://nav.al/feedback")
|
||||
naval_chat_bot.add("web_page", "https://nav.al/agi")
|
||||
naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44")
|
||||
naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf")
|
||||
naval_chat_bot.add("https://nav.al/feedback")
|
||||
naval_chat_bot.add("https://nav.al/agi")
|
||||
|
||||
# Embed Local Resources
|
||||
naval_chat_bot.add_local("qna_pair", ("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
|
||||
naval_chat_bot.add(("Who is Naval Ravikant?", "Naval Ravikant is an Indian-American entrepreneur and investor."))
|
||||
|
||||
naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?")
|
||||
# Answer: Naval argues that humans possess the unique capacity to understand explanations or concepts to the maximum extent possible in this physical reality.
|
||||
@@ -32,7 +32,7 @@ naval_chat_bot.query("What unique capacity does Naval argue humans possess when
|
||||
|
||||
Creating a chat bot over any dataset involves the following steps:
|
||||
|
||||
1. Load the data
|
||||
1. Detect the data type and load the data
|
||||
2. Create meaningful chunks
|
||||
3. Create embeddings for each chunk
|
||||
4. Store the chunks in a vector database
|
||||
@@ -53,4 +53,4 @@ The process of loading the dataset and querying involves multiple steps, each wi
|
||||
|
||||
Embedchain takes care of all these nuances and provides a simple interface to create bots over any dataset.
|
||||
|
||||
In the first release, we make it easier for anyone to get a chatbot over any dataset up and running in less than a minute. Just create an app instance, add the datasets using the `.add()` function, and use the `.query()` function to get the relevant answers.
|
||||
In the first release, we make it easier for anyone to get a chatbot over any dataset up and running in less than a minute. Just create an app instance, add the datasets using the `.add` method, and use the `.query` method to get the relevant answers.
|
||||
|
||||
+2
-2
@@ -32,11 +32,11 @@
|
||||
},
|
||||
{
|
||||
"group": "Advanced",
|
||||
"pages": ["advanced/app_types", "advanced/interface_types", "advanced/adding_data","advanced/data_types", "advanced/query_configuration", "advanced/configuration", "advanced/testing", "advanced/vector_database", "advanced/showcase"]
|
||||
"pages": ["advanced/app_types", "advanced/interface_types", "advanced/adding_data", "advanced/data_types", "advanced/query_configuration", "advanced/configuration", "advanced/testing", "advanced/vector_database", "advanced/showcase"]
|
||||
},
|
||||
{
|
||||
"group": "Examples",
|
||||
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot"]
|
||||
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot", "examples/slack_bot", "examples/telegram_bot", "examples/whatsapp_bot"]
|
||||
},
|
||||
{
|
||||
"group": "Contribution Guidelines",
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ os.environ["OPENAI_API_KEY"] = "xxx"
|
||||
elon_musk_bot = App()
|
||||
|
||||
# Embed Online Resources
|
||||
elon_musk_bot.add("web_page", "https://en.wikipedia.org/wiki/Elon_Musk")
|
||||
elon_musk_bot.add("web_page", "https://www.tesla.com/elon-musk")
|
||||
elon_musk_bot.add("https://en.wikipedia.org/wiki/Elon_Musk")
|
||||
elon_musk_bot.add("https://www.tesla.com/elon-musk")
|
||||
|
||||
response = elon_musk_bot.query("How many companies does Elon Musk run?")
|
||||
print(response)
|
||||
|
||||
@@ -25,6 +25,8 @@ class App(EmbedChain):
|
||||
|
||||
def get_llm_model_answer(self, prompt, config: ChatConfig):
|
||||
messages = []
|
||||
if config.system_prompt:
|
||||
messages.append({"role": "system", "content": config.system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
response = openai.ChatCompletion.create(
|
||||
model=config.model or "gpt-3.5-turbo-0613",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from langchain.schema import BaseMessage
|
||||
|
||||
@@ -68,7 +68,7 @@ class CustomApp(EmbedChain):
|
||||
return CustomApp._get_azure_openai_answer(prompt, config)
|
||||
|
||||
except ImportError as e:
|
||||
raise ImportError(e.msg) from None
|
||||
raise ModuleNotFoundError(e.msg) from None
|
||||
|
||||
@staticmethod
|
||||
def _get_openai_answer(prompt: str, config: ChatConfig) -> str:
|
||||
@@ -84,7 +84,7 @@ class CustomApp(EmbedChain):
|
||||
if config.top_p and config.top_p != 1:
|
||||
logging.warning("Config option `top_p` is not supported by this model.")
|
||||
|
||||
messages = CustomApp._get_messages(prompt)
|
||||
messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
|
||||
|
||||
return chat(messages).content
|
||||
|
||||
@@ -97,7 +97,7 @@ class CustomApp(EmbedChain):
|
||||
if config.max_tokens and config.max_tokens != 1000:
|
||||
logging.warning("Config option `max_tokens` is not supported by this model.")
|
||||
|
||||
messages = CustomApp._get_messages(prompt)
|
||||
messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
|
||||
|
||||
return chat(messages).content
|
||||
|
||||
@@ -110,7 +110,7 @@ class CustomApp(EmbedChain):
|
||||
if config.top_p and config.top_p != 1:
|
||||
logging.warning("Config option `top_p` is not supported by this model.")
|
||||
|
||||
messages = CustomApp._get_messages(prompt)
|
||||
messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
|
||||
|
||||
return chat(messages).content
|
||||
|
||||
@@ -133,15 +133,19 @@ class CustomApp(EmbedChain):
|
||||
if config.top_p and config.top_p != 1:
|
||||
logging.warning("Config option `top_p` is not supported by this model.")
|
||||
|
||||
messages = CustomApp._get_messages(prompt)
|
||||
messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
|
||||
|
||||
return chat(messages).content
|
||||
|
||||
@staticmethod
|
||||
def _get_messages(prompt: str) -> List[BaseMessage]:
|
||||
def _get_messages(prompt: str, system_prompt: Optional[str] = None) -> List[BaseMessage]:
|
||||
from langchain.schema import HumanMessage, SystemMessage
|
||||
|
||||
return [SystemMessage(content="You are a helpful assistant."), HumanMessage(content=prompt)]
|
||||
messages = []
|
||||
if system_prompt:
|
||||
messages.append(SystemMessage(content=system_prompt))
|
||||
messages.append(HumanMessage(content=prompt))
|
||||
return messages
|
||||
|
||||
def _stream_llm_model_response(self, response):
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
|
||||
from langchain.llms import Replicate
|
||||
|
||||
from embedchain.config import AppConfig
|
||||
from embedchain.config import AppConfig, ChatConfig
|
||||
from embedchain.embedchain import EmbedChain
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ class Llama2App(EmbedChain):
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
def get_llm_model_answer(self, prompt, config: AppConfig = None):
|
||||
def get_llm_model_answer(self, prompt, config: ChatConfig = None):
|
||||
# TODO: Move the model and other inputs into config
|
||||
if config.system_prompt:
|
||||
raise ValueError("Llama2App does not support `system_prompt`")
|
||||
llm = Replicate(
|
||||
model="a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
|
||||
input={"temperature": 0.75, "max_length": 500, "top_p": 1},
|
||||
|
||||
@@ -43,8 +43,8 @@ class OpenSourceApp(EmbedChain):
|
||||
try:
|
||||
from gpt4all import GPT4All
|
||||
except ModuleNotFoundError:
|
||||
raise ValueError(
|
||||
"The GPT4All python package is not installed. Please install it with `pip install GPT4All`"
|
||||
raise ModuleNotFoundError(
|
||||
"The GPT4All python package is not installed. Please install it with `pip install embedchain[opensource]`" # noqa E501
|
||||
) from None
|
||||
|
||||
return GPT4All(model)
|
||||
@@ -55,6 +55,9 @@ class OpenSourceApp(EmbedChain):
|
||||
"OpenSourceApp does not support switching models at runtime. Please create a new app instance."
|
||||
)
|
||||
|
||||
if config.system_prompt:
|
||||
raise ValueError("OpenSourceApp does not support `system_prompt`")
|
||||
|
||||
response = self.instance.generate(
|
||||
prompt=prompt,
|
||||
streaming=config.stream,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
from embedchain import CustomApp
|
||||
from embedchain.config import AddConfig, CustomAppConfig, QueryConfig
|
||||
from embedchain.models import EmbeddingFunctions, Providers
|
||||
|
||||
|
||||
class BaseBot:
|
||||
def __init__(self, app_config=None):
|
||||
if app_config is None:
|
||||
app_config = CustomAppConfig(embedding_fn=EmbeddingFunctions.OPENAI, provider=Providers.OPENAI)
|
||||
self.app_config = app_config
|
||||
self.app = CustomApp(config=self.app_config)
|
||||
|
||||
def add(self, data, config: AddConfig = None):
|
||||
"""Add data to the bot"""
|
||||
config = config if config else AddConfig()
|
||||
self.app.add(data, config=config)
|
||||
|
||||
def query(self, query, config: QueryConfig = None):
|
||||
"""Query bot"""
|
||||
config = config if config else QueryConfig()
|
||||
return self.app.query(query, config=config)
|
||||
|
||||
def start(self):
|
||||
"""Start the bot's functionality."""
|
||||
raise NotImplementedError("Subclasses must implement the start method.")
|
||||
@@ -0,0 +1,72 @@
|
||||
import argparse
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from flask import Flask, request
|
||||
from twilio.twiml.messaging_response import MessagingResponse
|
||||
|
||||
from .base import BaseBot
|
||||
|
||||
|
||||
class WhatsAppBot(BaseBot):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def handle_message(self, message):
|
||||
if message.startswith("add "):
|
||||
response = self.add_data(message)
|
||||
else:
|
||||
response = self.ask_bot(message)
|
||||
return response
|
||||
|
||||
def add_data(self, message):
|
||||
data = message.split(" ")[-1]
|
||||
try:
|
||||
self.add(data)
|
||||
response = f"Added data from: {data}"
|
||||
except Exception:
|
||||
logging.exception(f"Failed to add data {data}.")
|
||||
response = "Some error occurred while adding data."
|
||||
return response
|
||||
|
||||
def ask_bot(self, message):
|
||||
try:
|
||||
response = self.query(message)
|
||||
except Exception:
|
||||
logging.exception(f"Failed to query {message}.")
|
||||
response = "An error occurred. Please try again!"
|
||||
return response
|
||||
|
||||
def start(self, host="0.0.0.0", port=5000, debug=True):
|
||||
app = Flask(__name__)
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
logging.info("\nGracefully shutting down the WhatsAppBot...")
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
@app.route("/chat", methods=["POST"])
|
||||
def chat():
|
||||
incoming_message = request.values.get("Body", "").lower()
|
||||
response = self.handle_message(incoming_message)
|
||||
twilio_response = MessagingResponse()
|
||||
twilio_response.message(response)
|
||||
return str(twilio_response)
|
||||
|
||||
app.run(host=host, port=port, debug=debug)
|
||||
|
||||
|
||||
def start_command():
|
||||
parser = argparse.ArgumentParser(description="EmbedChain WhatsAppBot command line interface")
|
||||
parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind")
|
||||
parser.add_argument("--port", default=5000, type=int, help="Port to bind")
|
||||
args = parser.parse_args()
|
||||
|
||||
whatsapp_bot = WhatsAppBot()
|
||||
whatsapp_bot.start(host=args.host, port=args.port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_command()
|
||||
@@ -1,5 +1,7 @@
|
||||
import hashlib
|
||||
|
||||
from embedchain.models.data_type import DataType
|
||||
|
||||
|
||||
class BaseChunker:
|
||||
def __init__(self, text_splitter):
|
||||
@@ -26,7 +28,7 @@ class BaseChunker:
|
||||
|
||||
meta_data = data["meta_data"]
|
||||
# add data type to meta data to allow query using data type
|
||||
meta_data["data_type"] = self.data_type
|
||||
meta_data["data_type"] = self.data_type.value
|
||||
url = meta_data["url"]
|
||||
|
||||
chunks = self.get_chunks(content)
|
||||
@@ -52,8 +54,10 @@ class BaseChunker:
|
||||
"""
|
||||
return self.text_splitter.split_text(content)
|
||||
|
||||
def set_data_type(self, data_type):
|
||||
def set_data_type(self, data_type: DataType):
|
||||
"""
|
||||
set the data type of chunker
|
||||
"""
|
||||
self.data_type = data_type
|
||||
|
||||
# TODO: This should be done during initialization. This means it has to be done in the child classes.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from string import Template
|
||||
from typing import Optional
|
||||
|
||||
from embedchain.config.QueryConfig import QueryConfig
|
||||
|
||||
@@ -34,6 +35,7 @@ class ChatConfig(QueryConfig):
|
||||
top_p=None,
|
||||
stream: bool = False,
|
||||
deployment_name=None,
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initializes the ChatConfig instance.
|
||||
@@ -51,6 +53,8 @@ class ChatConfig(QueryConfig):
|
||||
(closer to 1) make word selection more diverse, lower values make words less
|
||||
diverse.
|
||||
:param stream: Optional. Control if response is streamed back to the user
|
||||
:param deployment_name: t.b.a.
|
||||
:param system_prompt: Optional. System prompt string.
|
||||
:raises ValueError: If the template is not valid as template should contain
|
||||
$context and $query and $history
|
||||
"""
|
||||
@@ -70,6 +74,7 @@ class ChatConfig(QueryConfig):
|
||||
history=[0],
|
||||
stream=stream,
|
||||
deployment_name=deployment_name,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
||||
def set_history(self, history):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import re
|
||||
from string import Template
|
||||
from typing import Optional
|
||||
|
||||
from embedchain.config.BaseConfig import BaseConfig
|
||||
|
||||
@@ -63,6 +64,7 @@ class QueryConfig(BaseConfig):
|
||||
history=None,
|
||||
stream: bool = False,
|
||||
deployment_name=None,
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initializes the QueryConfig instance.
|
||||
@@ -81,6 +83,8 @@ class QueryConfig(BaseConfig):
|
||||
diverse.
|
||||
:param history: Optional. A list of strings to consider as history.
|
||||
:param stream: Optional. Control if response is streamed back to user
|
||||
:param deployment_name: t.b.a.
|
||||
:param system_prompt: Optional. System prompt string.
|
||||
:raises ValueError: If the template is not valid as template should
|
||||
contain $context and $query (and optionally $history).
|
||||
"""
|
||||
@@ -108,6 +112,7 @@ class QueryConfig(BaseConfig):
|
||||
self.model = model
|
||||
self.top_p = top_p if top_p else 1
|
||||
self.deployment_name = deployment_name
|
||||
self.system_prompt = system_prompt
|
||||
|
||||
if self.validate_template(template):
|
||||
self.template = template
|
||||
|
||||
@@ -50,4 +50,10 @@ class OpenSourceAppConfig(BaseAppConfig):
|
||||
|
||||
:returns: The default embedding function
|
||||
"""
|
||||
return embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
|
||||
try:
|
||||
return embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
raise ModuleNotFoundError(
|
||||
"The open source app requires extra dependencies. Install with `pip install embedchain[opensource]`"
|
||||
) from None
|
||||
|
||||
@@ -15,6 +15,7 @@ from embedchain.loaders.pdf_file import PdfFileLoader
|
||||
from embedchain.loaders.sitemap import SitemapLoader
|
||||
from embedchain.loaders.web_page import WebPageLoader
|
||||
from embedchain.loaders.youtube_video import YoutubeVideoLoader
|
||||
from embedchain.models.data_type import DataType
|
||||
|
||||
|
||||
class DataFormatter:
|
||||
@@ -24,11 +25,11 @@ class DataFormatter:
|
||||
.add or .add_local method call
|
||||
"""
|
||||
|
||||
def __init__(self, data_type: str, config: AddConfig):
|
||||
def __init__(self, data_type: DataType, config: AddConfig):
|
||||
self.loader = self._get_loader(data_type, config.loader)
|
||||
self.chunker = self._get_chunker(data_type, config.chunker)
|
||||
|
||||
def _get_loader(self, data_type, config):
|
||||
def _get_loader(self, data_type: DataType, config):
|
||||
"""
|
||||
Returns the appropriate data loader for the given data type.
|
||||
|
||||
@@ -37,22 +38,22 @@ class DataFormatter:
|
||||
:raises ValueError: If an unsupported data type is provided.
|
||||
"""
|
||||
loaders = {
|
||||
"youtube_video": YoutubeVideoLoader,
|
||||
"pdf_file": PdfFileLoader,
|
||||
"web_page": WebPageLoader,
|
||||
"qna_pair": LocalQnaPairLoader,
|
||||
"text": LocalTextLoader,
|
||||
"docx": DocxFileLoader,
|
||||
"sitemap": SitemapLoader,
|
||||
"docs_site": DocsSiteLoader,
|
||||
DataType.YOUTUBE_VIDEO: YoutubeVideoLoader,
|
||||
DataType.PDF_FILE: PdfFileLoader,
|
||||
DataType.WEB_PAGE: WebPageLoader,
|
||||
DataType.QNA_PAIR: LocalQnaPairLoader,
|
||||
DataType.TEXT: LocalTextLoader,
|
||||
DataType.DOCX: DocxFileLoader,
|
||||
DataType.SITEMAP: SitemapLoader,
|
||||
DataType.DOCS_SITE: DocsSiteLoader,
|
||||
}
|
||||
lazy_loaders = ("notion",)
|
||||
lazy_loaders = {DataType.NOTION}
|
||||
if data_type in loaders:
|
||||
loader_class = loaders[data_type]
|
||||
loader = loader_class()
|
||||
return loader
|
||||
elif data_type in lazy_loaders:
|
||||
if data_type == "notion":
|
||||
if data_type == DataType.NOTION:
|
||||
from embedchain.loaders.notion import NotionLoader
|
||||
|
||||
return NotionLoader()
|
||||
@@ -61,7 +62,7 @@ class DataFormatter:
|
||||
else:
|
||||
raise ValueError(f"Unsupported data type: {data_type}")
|
||||
|
||||
def _get_chunker(self, data_type, config):
|
||||
def _get_chunker(self, data_type: DataType, config):
|
||||
"""
|
||||
Returns the appropriate chunker for the given data type.
|
||||
|
||||
@@ -70,15 +71,15 @@ class DataFormatter:
|
||||
:raises ValueError: If an unsupported data type is provided.
|
||||
"""
|
||||
chunker_classes = {
|
||||
"youtube_video": YoutubeVideoChunker,
|
||||
"pdf_file": PdfFileChunker,
|
||||
"web_page": WebPageChunker,
|
||||
"qna_pair": QnaPairChunker,
|
||||
"text": TextChunker,
|
||||
"docx": DocxFileChunker,
|
||||
"sitemap": WebPageChunker,
|
||||
"docs_site": DocsSiteChunker,
|
||||
"notion": NotionChunker,
|
||||
DataType.YOUTUBE_VIDEO: YoutubeVideoChunker,
|
||||
DataType.PDF_FILE: PdfFileChunker,
|
||||
DataType.WEB_PAGE: WebPageChunker,
|
||||
DataType.QNA_PAIR: QnaPairChunker,
|
||||
DataType.TEXT: TextChunker,
|
||||
DataType.DOCX: DocxFileChunker,
|
||||
DataType.WEB_PAGE: WebPageChunker,
|
||||
DataType.DOCS_SITE: DocsSiteChunker,
|
||||
DataType.NOTION: NotionChunker,
|
||||
}
|
||||
if data_type in chunker_classes:
|
||||
chunker_class = chunker_classes[data_type]
|
||||
|
||||
+97
-38
@@ -1,9 +1,10 @@
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import Optional
|
||||
import uuid
|
||||
from typing import Dict, Optional
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
@@ -17,6 +18,8 @@ from embedchain.config.apps.BaseAppConfig import BaseAppConfig
|
||||
from embedchain.config.QueryConfig import DOCS_SITE_PROMPT_TEMPLATE
|
||||
from embedchain.data_formatter import DataFormatter
|
||||
from embedchain.loaders.base_loader import BaseLoader
|
||||
from embedchain.models.data_type import DataType
|
||||
from embedchain.utils import detect_datatype
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -47,27 +50,62 @@ class EmbedChain:
|
||||
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("init",))
|
||||
thread_telemetry.start()
|
||||
|
||||
def add(self, data_type, url, metadata=None, config: AddConfig = None):
|
||||
def add(
|
||||
self,
|
||||
source,
|
||||
data_type: Optional[DataType] = None,
|
||||
metadata: Optional[Dict] = None,
|
||||
config: Optional[AddConfig] = None,
|
||||
):
|
||||
"""
|
||||
Adds the data from the given URL to the vector db.
|
||||
Loads the data, chunks it, create embedding for each chunk
|
||||
and then stores the embedding to vector database.
|
||||
|
||||
:param data_type: The type of the data to add.
|
||||
:param url: The URL where the data is located.
|
||||
:param source: The data to embed, can be a URL, local file or raw content, depending on the data type.
|
||||
:param data_type: Optional. Automatically detected, but can be forced with this argument.
|
||||
The type of the data to add.
|
||||
:param metadata: Optional. Metadata associated with the data source.
|
||||
:param config: Optional. The `AddConfig` instance to use as configuration
|
||||
options.
|
||||
:return: source_id, a md5-hash of the source, in hexadecimal representation.
|
||||
"""
|
||||
if config is None:
|
||||
config = AddConfig()
|
||||
|
||||
try:
|
||||
DataType(source)
|
||||
logging.warning(
|
||||
f"""Starting from version v0.0.40, Embedchain can automatically detect the data type. So, in the `add` method, the argument order has changed. You no longer need to specify '{source}' for the `source` argument. So the code snippet will be `.add("{data_type}", "{source}")`""" # noqa #E501
|
||||
)
|
||||
logging.warning(
|
||||
"Embedchain is swapping the arguments for you. This functionality might be deprecated in the future, so please adjust your code." # noqa #E501
|
||||
)
|
||||
source, data_type = data_type, source
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if data_type:
|
||||
try:
|
||||
data_type = DataType(data_type)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Invalid data_type: '{data_type}'.",
|
||||
f"Please use one of the following: {[data_type.value for data_type in DataType]}",
|
||||
) from None
|
||||
if not data_type:
|
||||
data_type = detect_datatype(source)
|
||||
|
||||
# `source_id` is the hash of the source argument
|
||||
hash_object = hashlib.md5(str(source).encode("utf-8"))
|
||||
source_id = hash_object.hexdigest()
|
||||
|
||||
data_formatter = DataFormatter(data_type, config)
|
||||
self.user_asks.append([data_type, url, metadata])
|
||||
self.user_asks.append([source, data_type.value, metadata])
|
||||
documents, _metadatas, _ids, new_chunks = self.load_and_embed(
|
||||
data_formatter.loader, data_formatter.chunker, url, metadata
|
||||
data_formatter.loader, data_formatter.chunker, source, metadata, source_id
|
||||
)
|
||||
if data_type in ("docs_site",):
|
||||
if data_type in {DataType.DOCS_SITE}:
|
||||
self.is_docs_site_instance = True
|
||||
|
||||
# Send anonymous telemetry
|
||||
@@ -75,35 +113,35 @@ class EmbedChain:
|
||||
# it's quicker to check the variable twice than to count words when they won't be submitted.
|
||||
word_count = sum([len(document.split(" ")) for document in documents])
|
||||
|
||||
extra_metadata = {"data_type": data_type, "word_count": word_count, "chunks_count": new_chunks}
|
||||
extra_metadata = {"data_type": data_type.value, "word_count": word_count, "chunks_count": new_chunks}
|
||||
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("add", extra_metadata))
|
||||
thread_telemetry.start()
|
||||
|
||||
def add_local(self, data_type, content, metadata=None, config: AddConfig = None):
|
||||
return source_id
|
||||
|
||||
def add_local(self, source, data_type=None, metadata=None, config: AddConfig = None):
|
||||
"""
|
||||
Adds the data you supply to the vector db.
|
||||
Warning:
|
||||
This method is deprecated and will be removed in future versions. Use `add` instead.
|
||||
|
||||
Adds the data from the given URL to the vector db.
|
||||
Loads the data, chunks it, create embedding for each chunk
|
||||
and then stores the embedding to vector database.
|
||||
|
||||
:param data_type: The type of the data to add.
|
||||
:param content: The local data. Refer to the `README` for formatting.
|
||||
:param source: The data to embed, can be a URL, local file or raw content, depending on the data type.
|
||||
:param data_type: Optional. Automatically detected, but can be forced with this argument.
|
||||
The type of the data to add.
|
||||
:param metadata: Optional. Metadata associated with the data source.
|
||||
:param config: Optional. The `AddConfig` instance to use as
|
||||
configuration options.
|
||||
:param config: Optional. The `AddConfig` instance to use as configuration
|
||||
options.
|
||||
:return: md5-hash of the source, in hexadecimal representation.
|
||||
"""
|
||||
if config is None:
|
||||
config = AddConfig()
|
||||
|
||||
data_formatter = DataFormatter(data_type, config)
|
||||
self.user_asks.append([data_type, content])
|
||||
self.load_and_embed(
|
||||
data_formatter.loader,
|
||||
data_formatter.chunker,
|
||||
content,
|
||||
metadata,
|
||||
logging.warning(
|
||||
"The `add_local` method is deprecated and will be removed in future versions. Please use the `add` method for both local and remote files." # noqa: E501
|
||||
)
|
||||
return self.add(source=source, data_type=data_type, metadata=metadata, config=config)
|
||||
|
||||
def load_and_embed(self, loader: BaseLoader, chunker: BaseChunker, src, metadata=None):
|
||||
def load_and_embed(self, loader: BaseLoader, chunker: BaseChunker, src, metadata=None, source_id=None):
|
||||
"""
|
||||
Loads the data from the given URL, chunks it, and adds it to database.
|
||||
|
||||
@@ -112,12 +150,16 @@ class EmbedChain:
|
||||
: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.
|
||||
:param source_id: Hexadecimal hash of the source.
|
||||
:return: (List) documents (embedded text), (List) metadata, (list) ids, (int) number of chunks
|
||||
"""
|
||||
embeddings_data = chunker.create_chunks(loader, src)
|
||||
|
||||
# spread chunking results
|
||||
documents = embeddings_data["documents"]
|
||||
metadatas = embeddings_data["metadatas"]
|
||||
ids = embeddings_data["ids"]
|
||||
|
||||
# get existing ids, and discard doc if any common id exist.
|
||||
where = {"app_id": self.config.id} if self.config.id is not None else {}
|
||||
# where={"url": src}
|
||||
@@ -138,22 +180,31 @@ class EmbedChain:
|
||||
ids = list(data_dict.keys())
|
||||
documents, metadatas = zip(*data_dict.values())
|
||||
|
||||
# Add app id in metadatas so that they can be queried on later
|
||||
if self.config.id is not None:
|
||||
metadatas = [{**m, "app_id": self.config.id} for m in metadatas]
|
||||
# Loop though all metadatas and add extras.
|
||||
new_metadatas = []
|
||||
for m in metadatas:
|
||||
# Add app id in metadatas so that they can be queried on later
|
||||
if self.config.id:
|
||||
m["app_id"] = self.config.id
|
||||
|
||||
# FIXME: Fix the error handling logic when metadatas or metadata is None
|
||||
metadatas = metadatas if metadatas else []
|
||||
metadata = metadata if metadata else {}
|
||||
# Add hashed source
|
||||
m["hash"] = source_id
|
||||
|
||||
# Note: Metadata is the function argument
|
||||
if metadata:
|
||||
# Spread whatever is in metadata into the new object.
|
||||
m.update(metadata)
|
||||
|
||||
new_metadatas.append(m)
|
||||
metadatas = new_metadatas
|
||||
|
||||
# Count before, to calculate a delta in the end.
|
||||
chunks_before_addition = self.count()
|
||||
|
||||
# Add metadata to each document
|
||||
metadatas_with_metadata = [{**meta, **metadata} for meta in metadatas]
|
||||
|
||||
self.db.add(documents=documents, metadatas=metadatas_with_metadata, ids=ids)
|
||||
self.db.add(documents=documents, metadatas=metadatas, ids=ids)
|
||||
count_new_chunks = self.count() - chunks_before_addition
|
||||
print((f"Successfully saved {src}. New chunks count: {count_new_chunks}"))
|
||||
return list(documents), metadatas_with_metadata, ids, count_new_chunks
|
||||
print((f"Successfully saved {src} ({chunker.data_type}). New chunks count: {count_new_chunks}"))
|
||||
return list(documents), metadatas, ids, count_new_chunks
|
||||
|
||||
def _format_result(self, results):
|
||||
return [
|
||||
@@ -366,13 +417,21 @@ class EmbedChain:
|
||||
def reset(self):
|
||||
"""
|
||||
Resets the database. Deletes all embeddings irreversibly.
|
||||
`App` has to be reinitialized after using this method.
|
||||
`App` does not have to be reinitialized after using this method.
|
||||
"""
|
||||
# Send anonymous telemetry
|
||||
thread_telemetry = threading.Thread(target=self._send_telemetry_event, args=("reset",))
|
||||
thread_telemetry.start()
|
||||
|
||||
collection_name = self.collection.name
|
||||
self.db.reset()
|
||||
self.collection = self.config.db._get_or_create_collection(collection_name)
|
||||
# Todo: Automatically recreating a collection with the same name cannot be the best way to handle a reset.
|
||||
# A downside of this implementation is, if you have two instances,
|
||||
# the other instance will not get the updated `self.collection` attribute.
|
||||
# A better way would be to create the collection if it is called again after being reset.
|
||||
# That means, checking if collection exists in the db-consuming methods, and creating it if it doesn't.
|
||||
# That's an extra steps for all uses, just to satisfy a niche use case in a niche method. For now, this will do.
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
|
||||
def _send_telemetry_event(self, method: str, extra_metadata: Optional[dict] = None):
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DataType(Enum):
|
||||
YOUTUBE_VIDEO = "youtube_video"
|
||||
PDF_FILE = "pdf_file"
|
||||
WEB_PAGE = "web_page"
|
||||
SITEMAP = "sitemap"
|
||||
DOCX = "docx"
|
||||
DOCS_SITE = "docs_site"
|
||||
TEXT = "text"
|
||||
QNA_PAIR = "qna_pair"
|
||||
NOTION = "notion"
|
||||
@@ -1,6 +1,10 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
from typing import Any
|
||||
|
||||
from embedchain.models.data_type import DataType
|
||||
|
||||
|
||||
def clean_string(text):
|
||||
@@ -89,3 +93,113 @@ def use_pysqlite3():
|
||||
"Error:",
|
||||
e,
|
||||
)
|
||||
__import__("pysqlite3")
|
||||
sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
|
||||
# Let the user know what happened.
|
||||
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
|
||||
print(
|
||||
f"{current_time} [embedchain] [INFO]",
|
||||
"Swapped std-lib sqlite3 with pysqlite3 for ChromaDb compatibility.",
|
||||
f"Your original version was {sqlite3.sqlite_version}.",
|
||||
)
|
||||
|
||||
|
||||
def format_source(source: str, limit: int = 20) -> str:
|
||||
"""
|
||||
Format a string to only take the first x and last x letters.
|
||||
This makes it easier to display a URL, keeping familiarity while ensuring a consistent length.
|
||||
If the string is too short, it is not sliced.
|
||||
"""
|
||||
if len(source) > 2 * limit:
|
||||
return source[:limit] + "..." + source[-limit:]
|
||||
return source
|
||||
|
||||
|
||||
def detect_datatype(source: Any) -> DataType:
|
||||
"""
|
||||
Automatically detect the datatype of the given source.
|
||||
|
||||
:param source: the source to base the detection on
|
||||
:return: data_type string
|
||||
"""
|
||||
from urllib.parse import urlparse
|
||||
|
||||
try:
|
||||
if not isinstance(source, str):
|
||||
raise ValueError("Source is not a string and thus cannot be a URL.")
|
||||
url = urlparse(source)
|
||||
# Check if both scheme and netloc are present. Local file system URIs are acceptable too.
|
||||
if not all([url.scheme, url.netloc]) and url.scheme != "file":
|
||||
raise ValueError("Not a valid URL.")
|
||||
except ValueError:
|
||||
url = False
|
||||
|
||||
formatted_source = format_source(str(source), 30)
|
||||
|
||||
if url:
|
||||
from langchain.document_loaders.youtube import \
|
||||
ALLOWED_NETLOCK as YOUTUBE_ALLOWED_NETLOCS
|
||||
|
||||
if url.netloc in YOUTUBE_ALLOWED_NETLOCS:
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `youtube_video`.")
|
||||
return DataType.YOUTUBE_VIDEO
|
||||
|
||||
if url.netloc in {"notion.so", "notion.site"}:
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `notion`.")
|
||||
return DataType.NOTION
|
||||
|
||||
if url.path.endswith(".pdf"):
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `pdf_file`.")
|
||||
return DataType.PDF_FILE
|
||||
|
||||
if url.path.endswith(".xml"):
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `sitemap`.")
|
||||
return DataType.SITEMAP
|
||||
|
||||
if url.path.endswith(".docx"):
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `docx`.")
|
||||
return DataType.DOCX
|
||||
|
||||
if "docs" in url.netloc or ("docs" in url.path and url.scheme != "file"):
|
||||
# `docs_site` detection via path is not accepted for local filesystem URIs,
|
||||
# because that would mean all paths that contain `docs` are now doc sites, which is too aggressive.
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `docs_site`.")
|
||||
return DataType.DOCS_SITE
|
||||
|
||||
# If none of the above conditions are met, it's a general web page
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `web_page`.")
|
||||
return DataType.WEB_PAGE
|
||||
|
||||
elif not isinstance(source, str):
|
||||
# For datatypes where source is not a string.
|
||||
|
||||
if isinstance(source, tuple) and len(source) == 2 and isinstance(source[0], str) and isinstance(source[1], str):
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `qna_pair`.")
|
||||
return DataType.QNA_PAIR
|
||||
|
||||
# Raise an error if it isn't a string and also not a valid non-string type (one of the previous).
|
||||
# We could stringify it, but it is better to raise an error and let the user decide how they want to do that.
|
||||
raise TypeError(
|
||||
"Source is not a string and a valid non-string type could not be detected. If you want to embed it, please stringify it, for instance by using `str(source)` or `(', ').join(source)`." # noqa: E501
|
||||
)
|
||||
|
||||
elif os.path.isfile(source):
|
||||
# For datatypes that support conventional file references.
|
||||
# Note: checking for string is not necessary anymore.
|
||||
|
||||
if source.endswith(".docx"):
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `docx`.")
|
||||
return DataType.DOCX
|
||||
|
||||
# If the source is a valid file, that's not detectable as a type, an error is raised.
|
||||
# It does not fallback to text.
|
||||
raise ValueError(
|
||||
"Source points to a valid file, but based on the filename, no `data_type` can be detected. Please be aware, that not all data_types allow conventional file references, some require the use of the `file URI scheme`. Please refer to the embedchain documentation (https://docs.embedchain.ai/advanced/data_types#remote-data-types)." # noqa: E501
|
||||
)
|
||||
|
||||
else:
|
||||
# Source is not a URL.
|
||||
|
||||
# Use text as final fallback.
|
||||
logging.debug(f"Source of `{formatted_source}` detected as `text`.")
|
||||
return DataType.TEXT
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# API Server
|
||||
|
||||
This is a docker template to create your own API Server using the embedchain package. To know more about the API Server and how to use it, go [here](https://docs.embedchain.ai/examples/api_server).
|
||||
@@ -26,6 +26,19 @@ def add():
|
||||
|
||||
@app.route("/query", methods=["POST"])
|
||||
def query():
|
||||
data = request.get_json()
|
||||
question = data.get("question")
|
||||
if question:
|
||||
try:
|
||||
response = chat_bot.query(question)
|
||||
return jsonify({"data": response}), 200
|
||||
except Exception:
|
||||
return jsonify({"error": "An error occurred. Please try again!"}), 500
|
||||
return jsonify({"error": "Invalid request. Please provide 'question' in JSON format."}), 400
|
||||
|
||||
|
||||
@app.route("/chat", methods=["POST"])
|
||||
def chat():
|
||||
data = request.get_json()
|
||||
question = data.get("question")
|
||||
if question:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Discord Bot
|
||||
|
||||
This is a docker template to create your own Discord bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/discord_bot).
|
||||
@@ -0,0 +1,7 @@
|
||||
__pycache__
|
||||
db
|
||||
database
|
||||
pyenv
|
||||
venv
|
||||
.env
|
||||
trash_files/
|
||||
@@ -0,0 +1,3 @@
|
||||
# Slack Bot
|
||||
|
||||
This is a replit template to create your own Slack bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/slack_bot).
|
||||
@@ -0,0 +1,5 @@
|
||||
flask==2.3.2
|
||||
slackeventsapi==3.0.1
|
||||
slacksdk==3.21.3
|
||||
python-dotenv==1.0.0
|
||||
embedchain
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask
|
||||
from slack_sdk import WebClient
|
||||
from slackeventsapi import SlackEventAdapter
|
||||
|
||||
from embedchain import App
|
||||
|
||||
load_dotenv()
|
||||
app = Flask(__name__)
|
||||
|
||||
slack_signing_secret = os.environ.get("SLACK_SIGNING_SECRET")
|
||||
slack_events_adapter = SlackEventAdapter(slack_signing_secret, "/chat", app)
|
||||
|
||||
slack_bot_token = os.environ.get("SLACK_BOT_TOKEN")
|
||||
client = WebClient(token=slack_bot_token)
|
||||
|
||||
chat_bot = App()
|
||||
recent_message = {"ts": 0, "channel": ""}
|
||||
|
||||
|
||||
@slack_events_adapter.on("message")
|
||||
def handle_message(event_data):
|
||||
message = event_data["event"]
|
||||
if "text" in message and message.get("subtype") != "bot_message":
|
||||
text = message["text"]
|
||||
if float(message.get("ts")) > float(recent_message["ts"]):
|
||||
recent_message["ts"] = message["ts"]
|
||||
recent_message["channel"] = message["channel"]
|
||||
if text.startswith("query"):
|
||||
_, question = text.split(" ", 1)
|
||||
try:
|
||||
response = chat_bot.chat(question)
|
||||
send_slack_message(message["channel"], response)
|
||||
print("Query answered successfully!")
|
||||
except Exception as e:
|
||||
send_slack_message(message["channel"], "An error occurred. Please try again!")
|
||||
print("Error occurred during 'query' command:", e)
|
||||
elif text.startswith("add"):
|
||||
_, data_type, url_or_text = text.split(" ", 2)
|
||||
if url_or_text.startswith("<") and url_or_text.endswith(">"):
|
||||
url_or_text = url_or_text[1:-1]
|
||||
try:
|
||||
chat_bot.add(data_type, url_or_text)
|
||||
send_slack_message(message["channel"], f"Added {data_type} : {url_or_text}")
|
||||
except Exception as e:
|
||||
send_slack_message(message["channel"], f"Failed to add {data_type} : {url_or_text}")
|
||||
print("Error occurred during 'add' command:", e)
|
||||
|
||||
|
||||
def send_slack_message(channel, message):
|
||||
response = client.chat_postMessage(channel=channel, text=message)
|
||||
return response
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||
@@ -0,0 +1,3 @@
|
||||
SLACK_SIGNING_SECRET=""
|
||||
SLACK_BOT_TOKEN=""
|
||||
OPENAI_API_KEY=""
|
||||
@@ -0,0 +1,7 @@
|
||||
__pycache__
|
||||
db
|
||||
database
|
||||
pyenv
|
||||
venv
|
||||
.env
|
||||
trash_files/
|
||||
@@ -0,0 +1,3 @@
|
||||
# Telegram Bot
|
||||
|
||||
This is a replit template to create your own Telegram bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/telegram_bot).
|
||||
@@ -0,0 +1,4 @@
|
||||
flask==2.3.2
|
||||
requests==2.31.0
|
||||
python-dotenv==1.0.0
|
||||
embedchain
|
||||
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, request
|
||||
|
||||
from embedchain import App
|
||||
|
||||
app = Flask(__name__)
|
||||
load_dotenv()
|
||||
bot_token = os.environ["TELEGRAM_BOT_TOKEN"]
|
||||
chat_bot = App()
|
||||
|
||||
|
||||
@app.route("/", methods=["POST"])
|
||||
def telegram_webhook():
|
||||
data = request.json
|
||||
message = data["message"]
|
||||
chat_id = message["chat"]["id"]
|
||||
text = message["text"]
|
||||
if text.startswith("/start"):
|
||||
response_text = (
|
||||
"Welcome to Embedchain Bot! Try the following commands to use the bot:\n"
|
||||
"For adding data sources:\n /add <data_type> <url_or_text>\n"
|
||||
"For asking queries:\n /query <question>"
|
||||
)
|
||||
elif text.startswith("/add"):
|
||||
_, data_type, url_or_text = text.split(maxsplit=2)
|
||||
response_text = add_to_chat_bot(data_type, url_or_text)
|
||||
elif text.startswith("/query"):
|
||||
_, question = text.split(maxsplit=1)
|
||||
response_text = query_chat_bot(question)
|
||||
else:
|
||||
response_text = "Invalid command. Please refer to the documentation for correct syntax."
|
||||
send_message(chat_id, response_text)
|
||||
return "OK"
|
||||
|
||||
|
||||
def add_to_chat_bot(data_type, url_or_text):
|
||||
try:
|
||||
chat_bot.add(data_type, url_or_text)
|
||||
response_text = f"Added {data_type} : {url_or_text}"
|
||||
except Exception as e:
|
||||
response_text = f"Failed to add {data_type} : {url_or_text}"
|
||||
print("Error occurred during 'add' command:", e)
|
||||
return response_text
|
||||
|
||||
|
||||
def query_chat_bot(question):
|
||||
try:
|
||||
response = chat_bot.chat(question)
|
||||
response_text = response
|
||||
except Exception as e:
|
||||
response_text = "An error occurred. Please try again!"
|
||||
print("Error occurred during 'query' command:", e)
|
||||
return response_text
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
|
||||
data = {"chat_id": chat_id, "text": text}
|
||||
requests.post(url, json=data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||
@@ -0,0 +1,2 @@
|
||||
TELEGRAM_BOT_TOKEN=""
|
||||
OPENAI_API_KEY=""
|
||||
@@ -0,0 +1,8 @@
|
||||
__pycache__
|
||||
db
|
||||
database
|
||||
pyenv
|
||||
venv
|
||||
.env
|
||||
trash_files/
|
||||
.ideas.md
|
||||
@@ -0,0 +1,3 @@
|
||||
# WhatsApp Bot
|
||||
|
||||
This is a replit template to create your own WhatsApp bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/whatsapp_bot).
|
||||
@@ -0,0 +1,3 @@
|
||||
Flask==2.3.2
|
||||
twilio==8.5.0
|
||||
embedchain
|
||||
@@ -0,0 +1,10 @@
|
||||
from embedchain.bots.whatsapp import WhatsAppBot
|
||||
|
||||
|
||||
def main():
|
||||
whatsapp_bot = WhatsAppBot()
|
||||
whatsapp_bot.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
OPENAI_API_KEY=""
|
||||
@@ -0,0 +1,51 @@
|
||||
from flask import Flask, request
|
||||
from twilio.twiml.messaging_response import MessagingResponse
|
||||
|
||||
from embedchain import App
|
||||
|
||||
app = Flask(__name__)
|
||||
chat_bot = App()
|
||||
|
||||
|
||||
@app.route("/chat", methods=["POST"])
|
||||
def chat():
|
||||
incoming_message = request.values.get("Body", "").lower()
|
||||
response = handle_message(incoming_message)
|
||||
twilio_response = MessagingResponse()
|
||||
twilio_response.message(response)
|
||||
return str(twilio_response)
|
||||
|
||||
|
||||
def handle_message(message):
|
||||
if message.startswith("add "):
|
||||
response = add_sources(message)
|
||||
else:
|
||||
response = query(message)
|
||||
return response
|
||||
|
||||
|
||||
def add_sources(message):
|
||||
message_parts = message.split(" ", 2)
|
||||
if len(message_parts) == 3:
|
||||
data_type = message_parts[1]
|
||||
url_or_text = message_parts[2]
|
||||
try:
|
||||
chat_bot.add(data_type, url_or_text)
|
||||
response = f"Added {data_type}: {url_or_text}"
|
||||
except Exception as e:
|
||||
response = f"Failed to add {data_type}: {url_or_text}.\nError: {str(e)}"
|
||||
else:
|
||||
response = "Invalid 'add' command format.\nUse: add <data_type> <url_or_text>"
|
||||
return response
|
||||
|
||||
|
||||
def query(message):
|
||||
try:
|
||||
response = chat_bot.chat(message)
|
||||
except Exception:
|
||||
response = "An error occurred. Please try again!"
|
||||
return response
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||
+8
-1
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "embedchain"
|
||||
version = "0.0.38"
|
||||
version = "0.0.43"
|
||||
description = "embedchain is a framework to easily create LLM powered bots over any dataset"
|
||||
authors = ["Taranjeet Singh"]
|
||||
license = "Apache License"
|
||||
@@ -91,7 +91,13 @@ beautifulsoup4 = "^4.12.2"
|
||||
pypdf = "^3.11.0"
|
||||
pytube = "^15.0.0"
|
||||
llama-index = { version = "^0.7.21", optional = true }
|
||||
sentence-transformers = { version = "^2.2.2", optional = true }
|
||||
torch = { version = ">=2.0.0, !=2.0.1", optional = true }
|
||||
# Torch 2.0.1 is not compatible with poetry (https://github.com/pytorch/pytorch/issues/100974)
|
||||
gpt4all = { version = "^1.0.8", optional = true }
|
||||
elasticsearch = { version = "^8.9.0", optional = true }
|
||||
flask = "^2.3.3"
|
||||
twilio = "^8.5.0"
|
||||
|
||||
|
||||
|
||||
@@ -108,6 +114,7 @@ isort = "^5.12.0"
|
||||
[tool.poetry.extras]
|
||||
streamlit = ["streamlit"]
|
||||
community = ["llama-index"]
|
||||
opensource = ["sentence-transformers", "torch", "gpt4all"]
|
||||
elasticsearch = ["elasticsearch"]
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import setuptools
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name="embedchain",
|
||||
version="0.0.38",
|
||||
author="Taranjeet Singh",
|
||||
author_email="reachtotj@gmail.com",
|
||||
description="embedchain is a framework to easily create LLM powered bots over any dataset", # noqa:E501
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/embedchain/embedchain",
|
||||
packages=setuptools.find_packages(),
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires=">=3.8",
|
||||
py_modules=["embedchain"],
|
||||
install_requires=[
|
||||
"langchain>=0.0.205",
|
||||
"requests",
|
||||
"openai",
|
||||
"chromadb>=0.4.2",
|
||||
"youtube-transcript-api",
|
||||
"beautifulsoup4",
|
||||
"pypdf",
|
||||
"pytube",
|
||||
"lxml",
|
||||
"gpt4all",
|
||||
"sentence_transformers",
|
||||
"docx2txt",
|
||||
"pydantic==1.10.8",
|
||||
"replicate==0.9.0",
|
||||
"duckduckgo-search==3.8.4",
|
||||
],
|
||||
extras_require={
|
||||
"dev": ["black", "ruff", "isort", "pytest"],
|
||||
"community": ["llama-index==0.7.21"],
|
||||
"elasticsearch": ["elasticsearch>=8.9.0"],
|
||||
},
|
||||
)
|
||||
@@ -4,6 +4,7 @@ import unittest
|
||||
|
||||
from embedchain.chunkers.text import TextChunker
|
||||
from embedchain.config import ChunkerConfig
|
||||
from embedchain.models.data_type import DataType
|
||||
|
||||
|
||||
class TestTextChunker(unittest.TestCase):
|
||||
@@ -15,6 +16,8 @@ class TestTextChunker(unittest.TestCase):
|
||||
chunker_config = ChunkerConfig(chunk_size=10, chunk_overlap=0, length_function=len)
|
||||
chunker = TextChunker(config=chunker_config)
|
||||
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
# Data type must be set manually in the test
|
||||
chunker.set_data_type(DataType.TEXT)
|
||||
|
||||
result = chunker.create_chunks(MockLoader(), text)
|
||||
|
||||
@@ -31,6 +34,8 @@ class TestTextChunker(unittest.TestCase):
|
||||
chunker_config = ChunkerConfig(chunk_size=9999999999, chunk_overlap=0, length_function=len)
|
||||
chunker = TextChunker(config=chunker_config)
|
||||
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
# Data type must be set manually in the test
|
||||
chunker.set_data_type(DataType.TEXT)
|
||||
|
||||
result = chunker.create_chunks(MockLoader(), text)
|
||||
|
||||
@@ -46,6 +51,8 @@ class TestTextChunker(unittest.TestCase):
|
||||
chunker = TextChunker(config=chunker_config)
|
||||
# We can't test with lorem ipsum because chunks are deduped, so would be recurring characters.
|
||||
text = """0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c"""
|
||||
# Data type must be set manually in the test
|
||||
chunker.set_data_type(DataType.TEXT)
|
||||
|
||||
result = chunker.create_chunks(MockLoader(), text)
|
||||
|
||||
|
||||
@@ -23,5 +23,14 @@ class TestApp(unittest.TestCase):
|
||||
The Collection.add method from the chromadb library is mocked during this test to isolate the behavior of the
|
||||
'add' method.
|
||||
"""
|
||||
self.app.add("web_page", "https://example.com", {"meta": "meta-data"})
|
||||
self.assertEqual(self.app.user_asks, [["web_page", "https://example.com", {"meta": "meta-data"}]])
|
||||
self.app.add("https://example.com", metadata={"meta": "meta-data"})
|
||||
self.assertEqual(self.app.user_asks, [["https://example.com", "web_page", {"meta": "meta-data"}]])
|
||||
|
||||
@patch("chromadb.api.models.Collection.Collection.add", MagicMock)
|
||||
def test_add_forced_type(self):
|
||||
"""
|
||||
Test that you can also force a data_type with `add`.
|
||||
"""
|
||||
data_type = "text"
|
||||
self.app.add("https://example.com", data_type=data_type, metadata={"meta": "meta-data"})
|
||||
self.assertEqual(self.app.user_asks, [["https://example.com", data_type, {"meta": "meta-data"}]])
|
||||
|
||||
@@ -31,9 +31,31 @@ class TestChromaDbHostsLoglevel(unittest.TestCase):
|
||||
|
||||
knowledge = "lorem ipsum dolor sit amet, consectetur adipiscing"
|
||||
|
||||
app.add_local("text", knowledge)
|
||||
app.add(knowledge, data_type="text")
|
||||
|
||||
app.query("What text did I give you?")
|
||||
app.chat("What text did I give you?")
|
||||
|
||||
self.assertEqual(mock_ec_get_llm_model_answer.call_args[1]["documents"], [knowledge])
|
||||
|
||||
def test_add_after_reset(self):
|
||||
"""
|
||||
Test if the `App` instance is correctly reconstructed after a reset.
|
||||
"""
|
||||
app = App()
|
||||
app.reset()
|
||||
|
||||
# Make sure the client is still healthy
|
||||
app.db.client.heartbeat()
|
||||
# Make sure the collection exists, and can be added to
|
||||
app.collection.add(
|
||||
embeddings=[[1.1, 2.3, 3.2], [4.5, 6.9, 4.4], [1.1, 2.3, 3.2]],
|
||||
metadatas=[
|
||||
{"chapter": "3", "verse": "16"},
|
||||
{"chapter": "3", "verse": "5"},
|
||||
{"chapter": "29", "verse": "11"},
|
||||
],
|
||||
ids=["id1", "id2", "id3"],
|
||||
)
|
||||
|
||||
app.reset()
|
||||
|
||||
@@ -41,3 +41,20 @@ class TestApp(unittest.TestCase):
|
||||
self.assertEqual(mock_retrieve.call_args[0][0], "Test query")
|
||||
self.assertIsInstance(mock_retrieve.call_args[0][1], QueryConfig)
|
||||
mock_answer.assert_called_once()
|
||||
|
||||
@patch("openai.ChatCompletion.create")
|
||||
def test_query_config_passing(self, mock_create):
|
||||
mock_create.return_value = {"choices": [{"message": {"content": "response"}}]} # Mock response
|
||||
|
||||
config = AppConfig()
|
||||
chat_config = QueryConfig(system_prompt="Test system prompt")
|
||||
app = App(config=config)
|
||||
|
||||
app.get_llm_model_answer("Test query", chat_config)
|
||||
|
||||
# Test systemp_prompt: Check that the 'create' method was called with the correct 'messages' argument
|
||||
messages_arg = mock_create.call_args.kwargs["messages"]
|
||||
self.assertEqual(messages_arg[0]["role"], "system")
|
||||
self.assertEqual(messages_arg[0]["content"], "Test system prompt")
|
||||
|
||||
# TODO: Add tests for other config variables
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from embedchain.models.data_type import DataType
|
||||
from embedchain.utils import detect_datatype
|
||||
|
||||
|
||||
class TestApp(unittest.TestCase):
|
||||
"""Test that the datatype detection is working, based on the input."""
|
||||
|
||||
def test_detect_datatype_youtube(self):
|
||||
self.assertEqual(detect_datatype("https://www.youtube.com/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO)
|
||||
self.assertEqual(detect_datatype("https://m.youtube.com/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO)
|
||||
self.assertEqual(
|
||||
detect_datatype("https://www.youtube-nocookie.com/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO
|
||||
)
|
||||
self.assertEqual(detect_datatype("https://vid.plus/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO)
|
||||
self.assertEqual(detect_datatype("https://youtu.be/dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO)
|
||||
|
||||
def test_detect_datatype_local_file(self):
|
||||
self.assertEqual(detect_datatype("file:///home/user/file.txt"), DataType.WEB_PAGE)
|
||||
|
||||
def test_detect_datatype_pdf(self):
|
||||
self.assertEqual(detect_datatype("https://www.example.com/document.pdf"), DataType.PDF_FILE)
|
||||
|
||||
def test_detect_datatype_local_pdf(self):
|
||||
self.assertEqual(detect_datatype("file:///home/user/document.pdf"), DataType.PDF_FILE)
|
||||
|
||||
def test_detect_datatype_xml(self):
|
||||
self.assertEqual(detect_datatype("https://www.example.com/sitemap.xml"), DataType.SITEMAP)
|
||||
|
||||
def test_detect_datatype_local_xml(self):
|
||||
self.assertEqual(detect_datatype("file:///home/user/sitemap.xml"), DataType.SITEMAP)
|
||||
|
||||
def test_detect_datatype_docx(self):
|
||||
self.assertEqual(detect_datatype("https://www.example.com/document.docx"), DataType.DOCX)
|
||||
|
||||
def test_detect_datatype_local_docx(self):
|
||||
self.assertEqual(detect_datatype("file:///home/user/document.docx"), DataType.DOCX)
|
||||
|
||||
@patch("os.path.isfile")
|
||||
def test_detect_datatype_regular_filesystem_docx(self, mock_isfile):
|
||||
with tempfile.NamedTemporaryFile(suffix=".docx", delete=True) as tmp:
|
||||
mock_isfile.return_value = True
|
||||
self.assertEqual(detect_datatype(tmp.name), DataType.DOCX)
|
||||
|
||||
def test_detect_datatype_docs_site(self):
|
||||
self.assertEqual(detect_datatype("https://docs.example.com"), DataType.DOCS_SITE)
|
||||
|
||||
def test_detect_datatype_docs_sitein_path(self):
|
||||
self.assertEqual(detect_datatype("https://www.example.com/docs/index.html"), DataType.DOCS_SITE)
|
||||
self.assertNotEqual(detect_datatype("file:///var/www/docs/index.html"), DataType.DOCS_SITE) # NOT equal
|
||||
|
||||
def test_detect_datatype_web_page(self):
|
||||
self.assertEqual(detect_datatype("https://nav.al/agi"), DataType.WEB_PAGE)
|
||||
|
||||
def test_detect_datatype_invalid_url(self):
|
||||
self.assertEqual(detect_datatype("not a url"), DataType.TEXT)
|
||||
|
||||
def test_detect_datatype_qna_pair(self):
|
||||
self.assertEqual(
|
||||
detect_datatype(("Question?", "Answer. Content of the string is irrelevant.")), DataType.QNA_PAIR
|
||||
) #
|
||||
|
||||
def test_detect_datatype_qna_pair_types(self):
|
||||
"""Test that a QnA pair needs to be a tuple of length two, and both items have to be strings."""
|
||||
with self.assertRaises(TypeError):
|
||||
self.assertNotEqual(
|
||||
detect_datatype(("How many planets are in our solar system?", 8)), DataType.QNA_PAIR
|
||||
) # NOT equal
|
||||
|
||||
def test_detect_datatype_text(self):
|
||||
self.assertEqual(detect_datatype("Just some text."), DataType.TEXT)
|
||||
|
||||
def test_detect_datatype_non_string_error(self):
|
||||
"""Test type error if the value passed is not a string, and not a valid non-string data_type"""
|
||||
with self.assertRaises(TypeError):
|
||||
detect_datatype(["foo", "bar"])
|
||||
|
||||
@patch("os.path.isfile")
|
||||
def test_detect_datatype_regular_filesystem_file_not_detected(self, mock_isfile):
|
||||
"""Test error if a valid file is referenced, but it isn't a valid data_type"""
|
||||
with tempfile.NamedTemporaryFile(suffix=".txt", delete=True) as tmp:
|
||||
mock_isfile.return_value = True
|
||||
with self.assertRaises(ValueError):
|
||||
detect_datatype(tmp.name)
|
||||
|
||||
def test_detect_datatype_regular_filesystem_no_file(self):
|
||||
"""Test that if a filepath is not actually an existing file, it is not handled as a file path."""
|
||||
self.assertEqual(detect_datatype("/var/not-an-existing-file.txt"), DataType.TEXT)
|
||||
|
||||
def test_doc_examples_quickstart(self):
|
||||
"""Test examples used in the documentation."""
|
||||
self.assertEqual(detect_datatype("https://en.wikipedia.org/wiki/Elon_Musk"), DataType.WEB_PAGE)
|
||||
self.assertEqual(detect_datatype("https://www.tesla.com/elon-musk"), DataType.WEB_PAGE)
|
||||
|
||||
def test_doc_examples_introduction(self):
|
||||
"""Test examples used in the documentation."""
|
||||
self.assertEqual(detect_datatype("https://www.youtube.com/watch?v=3qHkcs3kG44"), DataType.YOUTUBE_VIDEO)
|
||||
self.assertEqual(
|
||||
detect_datatype(
|
||||
"https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf"
|
||||
),
|
||||
DataType.PDF_FILE,
|
||||
)
|
||||
self.assertEqual(detect_datatype("https://nav.al/feedback"), DataType.WEB_PAGE)
|
||||
|
||||
def test_doc_examples_app_types(self):
|
||||
"""Test examples used in the documentation."""
|
||||
self.assertEqual(detect_datatype("https://www.youtube.com/watch?v=Ff4fRgnuFgQ"), DataType.YOUTUBE_VIDEO)
|
||||
self.assertEqual(detect_datatype("https://en.wikipedia.org/wiki/Mark_Zuckerberg"), DataType.WEB_PAGE)
|
||||
|
||||
def test_doc_examples_configuration(self):
|
||||
"""Test examples used in the documentation."""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "wikipedia"])
|
||||
import wikipedia
|
||||
|
||||
page = wikipedia.page("Albert Einstein")
|
||||
# TODO: Add a wikipedia type, so wikipedia is a dependency and we don't need this slow test.
|
||||
# (timings: import: 1.4s, fetch wiki: 0.7s)
|
||||
self.assertEqual(detect_datatype(page.content), DataType.TEXT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -245,8 +245,7 @@ class TestChromaDbCollection(unittest.TestCase):
|
||||
# Resetting the first one should reset them all.
|
||||
app1.reset()
|
||||
|
||||
# Reinstantiate them
|
||||
app1 = App(AppConfig(collection_name="one_collection", id="new_app_id_1", collect_metrics=False))
|
||||
# Reinstantiate app2-4, app1 doesn't have to be reinstantiated (PR #319)
|
||||
app2 = App(AppConfig(collection_name="one_collection", id="new_app_id_2", collect_metrics=False))
|
||||
app3 = App(AppConfig(collection_name="three_collection", id="new_app_id_3", collect_metrics=False))
|
||||
app4 = App(AppConfig(collection_name="four_collection", id="new_app_id_3", collect_metrics=False))
|
||||
|
||||
Reference in New Issue
Block a user