Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fcf2130b5 | ||
|
|
51df00729e | ||
|
|
023a61446f | ||
|
|
e0b73e6a5a | ||
|
|
28460f725c | ||
|
|
c93e49d2b8 | ||
|
|
07fb6bee54 | ||
|
|
3fa7db8420 | ||
|
|
c14bd7b73b | ||
|
|
5201beaab0 | ||
|
|
122313d8a5 | ||
|
|
82fd595306 | ||
|
|
95c0d47236 | ||
|
|
919cc74e94 | ||
|
|
d839991acb | ||
|
|
539286aafd | ||
|
|
23522b7b55 | ||
|
|
bf3fac56e4 | ||
|
|
a5bf8e9075 | ||
|
|
1d31b8f7e4 | ||
|
|
b144c7dccc | ||
|
|
1364975396 |
@@ -0,0 +1,8 @@
|
|||||||
|
llm:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
model: 'gpt-4'
|
||||||
|
temperature: 0.5
|
||||||
|
max_tokens: 1000
|
||||||
|
top_p: 1
|
||||||
|
stream: false
|
||||||
@@ -237,7 +237,7 @@ llm:
|
|||||||
Install related dependencies using the following command:
|
Install related dependencies using the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install --upgrade 'embedchain[huggingface_hub]'
|
pip install --upgrade 'embedchain[huggingface-hub]'
|
||||||
```
|
```
|
||||||
|
|
||||||
First, set `HUGGINGFACE_ACCESS_TOKEN` in environment variable which you can obtain from [their platform](https://huggingface.co/settings/tokens).
|
First, set `HUGGINGFACE_ACCESS_TOKEN` in environment variable which you can obtain from [their platform](https://huggingface.co/settings/tokens).
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
title: '🗨️ Discourse'
|
||||||
|
---
|
||||||
|
|
||||||
|
You can now easily load data from your community built with [Discourse](https://discourse.org/).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
1. Setup the Discourse Loader with your community url.
|
||||||
|
```Python
|
||||||
|
from embedchain.loaders.discourse import DiscourseLoader
|
||||||
|
|
||||||
|
dicourse_loader = DiscourseLoader(config={"domain": "https://community.openai.com"})
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Once you setup the loader, you can create an app and load data using the above discourse loader
|
||||||
|
```Python
|
||||||
|
import os
|
||||||
|
from embedchain.pipeline import Pipeline as App
|
||||||
|
|
||||||
|
os.environ["OPENAI_API_KEY"] = "sk-xxx"
|
||||||
|
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
app.add("openai after:2023-10-1", data_type="discourse", loader=dicourse_loader)
|
||||||
|
|
||||||
|
question = "Where can I find the OpenAI API status page?"
|
||||||
|
app.query(question)
|
||||||
|
# Answer: You can find the OpenAI API status page at https:/status.openai.com/.
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: The `add` function of the app will accept any executable search query to load data. Refer [Discourse API Docs](https://docs.discourse.org/#tag/Search) to learn more about search queries.
|
||||||
|
|
||||||
|
3. We automatically create a chunker to chunk your discourse data, however if you wish to provide your own chunker class. Here is how you can do that:
|
||||||
|
```Python
|
||||||
|
|
||||||
|
from embedchain.chunkers.discourse import DiscourseChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
|
||||||
|
discourse_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len)
|
||||||
|
discourse_chunker = DiscourseChunker(config=discourse_chunker_config)
|
||||||
|
|
||||||
|
app.add("openai", data_type='discourse', loader=dicourse_loader, chunker=discourse_chunker)
|
||||||
|
```
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: '🐬 MySQL'
|
||||||
|
---
|
||||||
|
|
||||||
|
1. Setup the MySQL loader by configuring the SQL db.
|
||||||
|
```Python
|
||||||
|
from embedchain.loaders.mysql import MySQLLoader
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"host": "host",
|
||||||
|
"port": "port",
|
||||||
|
"database": "database",
|
||||||
|
"user": "username",
|
||||||
|
"password": "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_loader = MySQLLoader(config=config)
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details on how to setup with valid config, check MySQL [documentation](https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html).
|
||||||
|
|
||||||
|
2. Once you setup the loader, you can create an app and load data using the above MySQL loader
|
||||||
|
```Python
|
||||||
|
import os
|
||||||
|
from embedchain.pipeline import Pipeline as App
|
||||||
|
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
app.add("SELECT * FROM table_name;", data_type='mysql', loader=mysql_loader)
|
||||||
|
# Adds `(1, 'What is your net worth, Elon Musk?', "As of October 2023, Elon Musk's net worth is $255.2 billion.")`
|
||||||
|
|
||||||
|
response = app.query(question)
|
||||||
|
# Answer: As of October 2023, Elon Musk's net worth is $255.2 billion.
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: The `add` function of the app will accept any executable query to load data. DO NOT pass the `CREATE`, `INSERT` queries in `add` function.
|
||||||
|
|
||||||
|
3. We automatically create a chunker to chunk your SQL data, however if you wish to provide your own chunker class. Here is how you can do that:
|
||||||
|
``Python
|
||||||
|
|
||||||
|
from embedchain.chunkers.mysql import MySQLChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
|
||||||
|
mysql_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len)
|
||||||
|
mysql_chunker = MySQLChunker(config=mysql_chunker_config)
|
||||||
|
|
||||||
|
app.add("SELECT * FROM table_name;", data_type='mysql', loader=mysql_loader, chunker=mysql_chunker)
|
||||||
|
```
|
||||||
@@ -18,9 +18,12 @@ Embedchain comes with built-in support for various data sources. We handle the c
|
|||||||
<Card title="🌐📄 web page" href="/data-sources/web-page"></Card>
|
<Card title="🌐📄 web page" href="/data-sources/web-page"></Card>
|
||||||
<Card title="🧾 xml" href="/data-sources/xml"></Card>
|
<Card title="🧾 xml" href="/data-sources/xml"></Card>
|
||||||
<Card title="🙌 OpenAPI" href="/data-sources/openapi"></Card>
|
<Card title="🙌 OpenAPI" href="/data-sources/openapi"></Card>
|
||||||
<Card title="🎥📺 youtube video" href="/data-sources/youtube-video"></Card>
|
<Card title="📺 youtube video" href="/data-sources/youtube-video"></Card>
|
||||||
<Card title="📬 Gmail" href="/data-sources/gmail"></Card>
|
<Card title="📬 Gmail" href="/data-sources/gmail"></Card>
|
||||||
<Card title="🐘 Postgres" href="/data-sources/postgres"></Card>
|
<Card title="🐘 Postgres" href="/data-sources/postgres"></Card>
|
||||||
|
<Card title="🐬 MySQL" href="/data-sources/mysql"></Card>
|
||||||
|
<Card title="🤖 Slack" href="/data-sources/slack"></Card>
|
||||||
|
<Card title="🗨️ Discourse" href="/data-sources/discourse"></Card>
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
||||||
<br/ >
|
<br/ >
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: '🤖 Slack'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-requisite
|
||||||
|
- Download required packages by running `pip install --upgrade "embedchain[slack]"`.
|
||||||
|
- Configure your slack bot token as environment variable `SLACK_USER_TOKEN`.
|
||||||
|
- Find your user token on your [Slack Account](https://api.slack.com/authentication/token-types)
|
||||||
|
- Make sure your slack user token includes [search](https://api.slack.com/scopes/search:read) scope.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
1. Setup the Slack loader by configuring the Slack Webclient.
|
||||||
|
```Python
|
||||||
|
from embedchain.loaders.slack import SlackLoader
|
||||||
|
|
||||||
|
os.environ["SLACK_USER_TOKEN"] = "xoxp-*"
|
||||||
|
|
||||||
|
loader = SlackLoader()
|
||||||
|
|
||||||
|
"""
|
||||||
|
config = {
|
||||||
|
'base_url': slack_app_url,
|
||||||
|
'headers': web_headers,
|
||||||
|
'team_id': slack_team_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
loader = SlackLoader(config)
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: you can also pass the `config` with `base_url`, `headers`, `team_id` to setup your SlackLoader.
|
||||||
|
|
||||||
|
2. Once you setup the loader, you can create an app and load data using the above slack loader
|
||||||
|
```Python
|
||||||
|
import os
|
||||||
|
from embedchain.pipeline import Pipeline as App
|
||||||
|
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
app.add("in:random", data_type="slack", loader=loader)
|
||||||
|
question = "Which bots are available in the slack workspace's random channel?"
|
||||||
|
# Answer: The available bot in the slack workspace's random channel is the Embedchain bot.
|
||||||
|
```
|
||||||
|
|
||||||
|
3. We automatically create a chunker to chunk your slack data, however if you wish to provide your own chunker class. Here is how you can do that:
|
||||||
|
```Python
|
||||||
|
from embedchain.chunkers.slack import SlackChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
|
||||||
|
slack_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len)
|
||||||
|
slack_chunker = SlackChunker(config=slack_chunker_config)
|
||||||
|
|
||||||
|
app.add(slack_chunker, data_type="slack", loader=loader, chunker=slack_chunker)
|
||||||
|
```
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "📝 Substack"
|
||||||
|
---
|
||||||
|
|
||||||
|
To add any Substack data sources to your app, just add the sitemap.xml of that url as the source and set the data_type to `substack`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from embedchain import Pipeline as App
|
||||||
|
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
# source: for any substack just add the sitemap.xml url
|
||||||
|
app.add('https://www.lennysnewsletter.com/sitemap.xml', data_type='substack')
|
||||||
|
app.query("Who is Brian Chesky?")
|
||||||
|
# Answer: Brian Chesky is the co-founder and CEO of Airbnb.
|
||||||
|
```
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: '🎥📺 Youtube video'
|
title: '📺 Youtube video'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,21 @@ channels:read
|
|||||||
chat:write
|
chat:write
|
||||||
```
|
```
|
||||||
5. 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`.
|
5. 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`.
|
||||||
6. Run your bot now with `python3 -m embedchain.bots.slack`
|
6. Run your bot now,
|
||||||
7. Expose your bot to the internet. Default port is `5000`, which can be changed by adding `port --8080` to the startup command. You can use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible.
|
<Tabs>
|
||||||
|
<Tab title="docker">
|
||||||
|
```bash
|
||||||
|
docker run --name slack-bot -e OPENAI_API_KEY=sk-xxx -e SLACK_BOT_TOKEN=xxx -p 8000:8000 embedchain/slack-bot
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab title="python">
|
||||||
|
```bash
|
||||||
|
pip install --upgrade "embedchain[slack]"
|
||||||
|
python3 -m embedchain.bots.slack --port 8000
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
7. Expose your bot to the internet. You can use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible.
|
||||||
8. On the Slack API website go to `Event Subscriptions` on the left Sidebar and turn on `Enable Events`.
|
8. On the Slack API website go to `Event Subscriptions` on the left Sidebar and turn on `Enable Events`.
|
||||||
9. In `Request URL`, enter your server or ngrok address.
|
9. In `Request URL`, enter your server or ngrok address.
|
||||||
10. After it gets verified, click on `Subscribe to bot events`, add `message.channels` Bot User Event and click on `Save Changes`.
|
10. After it gets verified, click on `Subscribe to bot events`, add `message.channels` Bot User Event and click on `Save Changes`.
|
||||||
|
|||||||
@@ -83,3 +83,9 @@ app.deploy()
|
|||||||
# 🛠️ Adding data to your pipeline...
|
# 🛠️ Adding data to your pipeline...
|
||||||
# ✅ Data of type: web_page, value: https://www.forbes.com/profile/elon-musk added successfully.
|
# ✅ Data of type: web_page, value: https://www.forbes.com/profile/elon-musk added successfully.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can try it out yourself using the following Google Colab notebook:
|
||||||
|
|
||||||
|
<a href="https://colab.research.google.com/drive/17ON1LPonnXAtLaZEebnOktstB_1cJJmh?usp=sharing">
|
||||||
|
<img src="https://camo.githubusercontent.com/84f0493939e0c4de4e6dbe113251b4bfb5353e57134ffd9fcab6b8714514d4d1/68747470733a2f2f636f6c61622e72657365617263682e676f6f676c652e636f6d2f6173736574732f636f6c61622d62616467652e737667" alt="Open in Colab" />
|
||||||
|
</a>
|
||||||
|
|||||||
+3
-1
@@ -87,7 +87,9 @@
|
|||||||
"data-sources/text",
|
"data-sources/text",
|
||||||
"data-sources/web-page",
|
"data-sources/web-page",
|
||||||
"data-sources/openapi",
|
"data-sources/openapi",
|
||||||
"data-sources/youtube-video"
|
"data-sources/youtube-video",
|
||||||
|
"data-sources/discourse",
|
||||||
|
"data-sources/substack"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"data-sources/data-type-handling"
|
"data-sources/data-type-handling"
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ class App(EmbedChain):
|
|||||||
:return: An instance of the App class.
|
:return: An instance of the App class.
|
||||||
:rtype: App
|
:rtype: App
|
||||||
"""
|
"""
|
||||||
|
# Setup user directory if it doesn't exist already
|
||||||
|
Client.setup_dir()
|
||||||
|
|
||||||
with open(yaml_path, "r") as file:
|
with open(yaml_path, "r") as file:
|
||||||
config_data = yaml.safe_load(file)
|
config_data = yaml.safe_load(file)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
from embedchain.chunkers.base_chunker import BaseChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
|
|
||||||
|
|
||||||
|
@register_deserializable
|
||||||
|
class CommonChunker(BaseChunker):
|
||||||
|
"""Common chunker for all loaders."""
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
from embedchain.chunkers.base_chunker import BaseChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
|
|
||||||
|
|
||||||
|
@register_deserializable
|
||||||
|
class DiscourseChunker(BaseChunker):
|
||||||
|
"""Chunker for discourse."""
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
from embedchain.chunkers.base_chunker import BaseChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
|
|
||||||
|
|
||||||
|
@register_deserializable
|
||||||
|
class MySQLChunker(BaseChunker):
|
||||||
|
"""Chunker for json."""
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
from embedchain.chunkers.base_chunker import BaseChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
|
|
||||||
|
|
||||||
|
@register_deserializable
|
||||||
|
class SlackChunker(BaseChunker):
|
||||||
|
"""Chunker for postgres."""
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
from embedchain.chunkers.base_chunker import BaseChunker
|
||||||
|
from embedchain.config.add_config import ChunkerConfig
|
||||||
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
|
|
||||||
|
|
||||||
|
@register_deserializable
|
||||||
|
class SubstackChunker(BaseChunker):
|
||||||
|
"""Chunker for Substack."""
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -63,11 +63,17 @@ class DataFormatter(JSONSerializable):
|
|||||||
DataType.OPENAPI: "embedchain.loaders.openapi.OpenAPILoader",
|
DataType.OPENAPI: "embedchain.loaders.openapi.OpenAPILoader",
|
||||||
DataType.GMAIL: "embedchain.loaders.gmail.GmailLoader",
|
DataType.GMAIL: "embedchain.loaders.gmail.GmailLoader",
|
||||||
DataType.NOTION: "embedchain.loaders.notion.NotionLoader",
|
DataType.NOTION: "embedchain.loaders.notion.NotionLoader",
|
||||||
|
DataType.SUBSTACK: "embedchain.loaders.substack.SubstackLoader",
|
||||||
|
DataType.GITHUB: "embedchain.loaders.github.GithubLoader",
|
||||||
|
DataType.YOUTUBE_CHANNEL: "embedchain.loaders.youtube_channel.YoutubeChannelLoader",
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_loaders = set(
|
custom_loaders = set(
|
||||||
[
|
[
|
||||||
DataType.POSTGRES,
|
DataType.POSTGRES,
|
||||||
|
DataType.MYSQL,
|
||||||
|
DataType.SLACK,
|
||||||
|
DataType.DISCOURSE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,6 +112,12 @@ class DataFormatter(JSONSerializable):
|
|||||||
DataType.GMAIL: "embedchain.chunkers.gmail.GmailChunker",
|
DataType.GMAIL: "embedchain.chunkers.gmail.GmailChunker",
|
||||||
DataType.NOTION: "embedchain.chunkers.notion.NotionChunker",
|
DataType.NOTION: "embedchain.chunkers.notion.NotionChunker",
|
||||||
DataType.POSTGRES: "embedchain.chunkers.postgres.PostgresChunker",
|
DataType.POSTGRES: "embedchain.chunkers.postgres.PostgresChunker",
|
||||||
|
DataType.MYSQL: "embedchain.chunkers.mysql.MySQLChunker",
|
||||||
|
DataType.SLACK: "embedchain.chunkers.slack.SlackChunker",
|
||||||
|
DataType.DISCOURSE: "embedchain.chunkers.discourse.DiscourseChunker",
|
||||||
|
DataType.SUBSTACK: "embedchain.chunkers.substack.SubstackChunker",
|
||||||
|
DataType.GITHUB: "embedchain.chunkers.common_chunker.CommonChunker",
|
||||||
|
DataType.YOUTUBE_CHANNEL: "embedchain.chunkers.common_chunker.CommonChunker",
|
||||||
}
|
}
|
||||||
|
|
||||||
if data_type in chunker_classes:
|
if data_type in chunker_classes:
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ class EmbedChain(JSONSerializable):
|
|||||||
self.user_asks.append([source, data_type.value, metadata])
|
self.user_asks.append([source, data_type.value, metadata])
|
||||||
|
|
||||||
data_formatter = DataFormatter(data_type, config, kwargs)
|
data_formatter = DataFormatter(data_type, config, kwargs)
|
||||||
documents, metadatas, _ids, new_chunks = self.load_and_embed(
|
documents, metadatas, _ids, new_chunks = self._load_and_embed(
|
||||||
data_formatter.loader, data_formatter.chunker, source, metadata, source_hash, dry_run
|
data_formatter.loader, data_formatter.chunker, source, metadata, source_hash, dry_run
|
||||||
)
|
)
|
||||||
if data_type in {DataType.DOCS_SITE}:
|
if data_type in {DataType.DOCS_SITE}:
|
||||||
@@ -340,7 +340,7 @@ class EmbedChain(JSONSerializable):
|
|||||||
"When it should be DirectDataType, IndirectDataType or SpecialDataType."
|
"When it should be DirectDataType, IndirectDataType or SpecialDataType."
|
||||||
)
|
)
|
||||||
|
|
||||||
def load_and_embed(
|
def _load_and_embed(
|
||||||
self,
|
self,
|
||||||
loader: BaseLoader,
|
loader: BaseLoader,
|
||||||
chunker: BaseChunker,
|
chunker: BaseChunker,
|
||||||
@@ -457,7 +457,7 @@ class EmbedChain(JSONSerializable):
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
def retrieve_from_database(
|
def _retrieve_from_database(
|
||||||
self, input_query: str, config: Optional[BaseLlmConfig] = None, where=None, citations: bool = False
|
self, input_query: str, config: Optional[BaseLlmConfig] = None, where=None, citations: bool = False
|
||||||
) -> Union[List[Tuple[str, str, str]], List[str]]:
|
) -> Union[List[Tuple[str, str, str]], List[str]]:
|
||||||
"""
|
"""
|
||||||
@@ -482,7 +482,7 @@ class EmbedChain(JSONSerializable):
|
|||||||
where = {}
|
where = {}
|
||||||
if query_config is not None and query_config.where is not None:
|
if query_config is not None and query_config.where is not None:
|
||||||
where = query_config.where
|
where = query_config.where
|
||||||
|
|
||||||
if self.config.id is not None:
|
if self.config.id is not None:
|
||||||
where.update({"app_id": self.config.id})
|
where.update({"app_id": self.config.id})
|
||||||
|
|
||||||
@@ -537,7 +537,9 @@ class EmbedChain(JSONSerializable):
|
|||||||
:rtype: str, if citations is False, otherwise Tuple[str,List[Tuple[str,str,str]]]
|
:rtype: str, if citations is False, otherwise Tuple[str,List[Tuple[str,str,str]]]
|
||||||
"""
|
"""
|
||||||
citations = kwargs.get("citations", False)
|
citations = kwargs.get("citations", False)
|
||||||
contexts = self.retrieve_from_database(input_query=input_query, config=config, where=where, citations=citations)
|
contexts = self._retrieve_from_database(
|
||||||
|
input_query=input_query, config=config, where=where, citations=citations
|
||||||
|
)
|
||||||
if citations and len(contexts) > 0 and isinstance(contexts[0], tuple):
|
if citations and len(contexts) > 0 and isinstance(contexts[0], tuple):
|
||||||
contexts_data_for_llm_query = list(map(lambda x: x[0], contexts))
|
contexts_data_for_llm_query = list(map(lambda x: x[0], contexts))
|
||||||
else:
|
else:
|
||||||
@@ -588,7 +590,9 @@ class EmbedChain(JSONSerializable):
|
|||||||
:rtype: str, if citations is False, otherwise Tuple[str,List[Tuple[str,str,str]]]
|
:rtype: str, if citations is False, otherwise Tuple[str,List[Tuple[str,str,str]]]
|
||||||
"""
|
"""
|
||||||
citations = kwargs.get("citations", False)
|
citations = kwargs.get("citations", False)
|
||||||
contexts = self.retrieve_from_database(input_query=input_query, config=config, where=where, citations=citations)
|
contexts = self._retrieve_from_database(
|
||||||
|
input_query=input_query, config=config, where=where, citations=citations
|
||||||
|
)
|
||||||
if citations and len(contexts) > 0 and isinstance(contexts[0], tuple):
|
if citations and len(contexts) > 0 and isinstance(contexts[0], tuple):
|
||||||
contexts_data_for_llm_query = list(map(lambda x: x[0], contexts))
|
contexts_data_for_llm_query = list(map(lambda x: x[0], contexts))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
"""
|
|
||||||
Note that this file is copied from Chroma repository. We will remove this file once the fix in
|
|
||||||
ChromaDB's repository.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from chromadb.api.types import Documents, Embeddings
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIEmbeddingFunction:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
api_key: Optional[str] = None,
|
|
||||||
model_name: str = "text-embedding-ada-002",
|
|
||||||
organization_id: Optional[str] = None,
|
|
||||||
api_base: Optional[str] = None,
|
|
||||||
api_type: Optional[str] = None,
|
|
||||||
api_version: Optional[str] = None,
|
|
||||||
deployment_id: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Initialize the OpenAIEmbeddingFunction.
|
|
||||||
Args:
|
|
||||||
api_key (str, optional): Your API key for the OpenAI API. If not
|
|
||||||
provided, it will raise an error to provide an OpenAI API key.
|
|
||||||
organization_id(str, optional): The OpenAI organization ID if applicable
|
|
||||||
model_name (str, optional): The name of the model to use for text
|
|
||||||
embeddings. Defaults to "text-embedding-ada-002".
|
|
||||||
api_base (str, optional): The base path for the API. If not provided,
|
|
||||||
it will use the base path for the OpenAI API. This can be used to
|
|
||||||
point to a different deployment, such as an Azure deployment.
|
|
||||||
api_type (str, optional): The type of the API deployment. This can be
|
|
||||||
used to specify a different deployment, such as 'azure'. If not
|
|
||||||
provided, it will use the default OpenAI deployment.
|
|
||||||
api_version (str, optional): The api version for the API. If not provided,
|
|
||||||
it will use the api version for the OpenAI API. This can be used to
|
|
||||||
point to a different deployment, such as an Azure deployment.
|
|
||||||
deployment_id (str, optional): Deployment ID for Azure OpenAI.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import openai
|
|
||||||
except ImportError:
|
|
||||||
raise ValueError("The openai python package is not installed. Please install it with `pip install openai`")
|
|
||||||
|
|
||||||
if api_key is not None:
|
|
||||||
openai.api_key = api_key
|
|
||||||
# If the api key is still not set, raise an error
|
|
||||||
elif openai.api_key is None:
|
|
||||||
raise ValueError(
|
|
||||||
"Please provide an OpenAI API key. You can get one at https://platform.openai.com/account/api-keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
if api_base is not None:
|
|
||||||
openai.api_base = api_base
|
|
||||||
|
|
||||||
if api_version is not None:
|
|
||||||
openai.api_version = api_version
|
|
||||||
|
|
||||||
self._api_type = api_type
|
|
||||||
if api_type is not None:
|
|
||||||
openai.api_type = api_type
|
|
||||||
|
|
||||||
if organization_id is not None:
|
|
||||||
openai.organization = organization_id
|
|
||||||
|
|
||||||
self._v1 = openai.__version__.startswith("1.")
|
|
||||||
if self._v1:
|
|
||||||
if api_type == "azure":
|
|
||||||
self._client = openai.AzureOpenAI(
|
|
||||||
api_key=api_key, api_version=api_version, azure_endpoint=api_base
|
|
||||||
).embeddings
|
|
||||||
else:
|
|
||||||
self._client = openai.OpenAI(api_key=api_key, base_url=api_base).embeddings
|
|
||||||
else:
|
|
||||||
self._client = openai.Embedding
|
|
||||||
self._model_name = model_name
|
|
||||||
self._deployment_id = deployment_id
|
|
||||||
|
|
||||||
def __call__(self, input: Documents) -> Embeddings:
|
|
||||||
# replace newlines, which can negatively affect performance.
|
|
||||||
input = [t.replace("\n", " ") for t in input]
|
|
||||||
|
|
||||||
# Call the OpenAI Embedding API
|
|
||||||
if self._v1:
|
|
||||||
embeddings = self._client.create(input=input, model=self._deployment_id or self._model_name).data
|
|
||||||
|
|
||||||
# Sort resulting embeddings by index
|
|
||||||
sorted_embeddings = sorted(embeddings, key=lambda e: e.index) # type: ignore
|
|
||||||
|
|
||||||
# Return just the embeddings
|
|
||||||
return [result.embedding for result in sorted_embeddings]
|
|
||||||
else:
|
|
||||||
if self._api_type == "azure":
|
|
||||||
embeddings = self._client.create(input=input, engine=self._deployment_id or self._model_name)["data"]
|
|
||||||
else:
|
|
||||||
embeddings = self._client.create(input=input, model=self._model_name)["data"]
|
|
||||||
|
|
||||||
# Sort resulting embeddings by index
|
|
||||||
sorted_embeddings = sorted(embeddings, key=lambda e: e["index"]) # type: ignore
|
|
||||||
|
|
||||||
# Return just the embeddings
|
|
||||||
return [result["embedding"] for result in sorted_embeddings]
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import os
|
import os
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
|
||||||
from langchain.embeddings import OpenAIEmbeddings
|
from langchain.embeddings import OpenAIEmbeddings
|
||||||
|
|
||||||
from embedchain.config import BaseEmbedderConfig
|
from embedchain.config import BaseEmbedderConfig
|
||||||
from embedchain.embedder.base import BaseEmbedder
|
from embedchain.embedder.base import BaseEmbedder
|
||||||
from embedchain.models import VectorDimensions
|
from embedchain.models import VectorDimensions
|
||||||
|
|
||||||
from .chroma_embeddings import OpenAIEmbeddingFunction
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIEmbedder(BaseEmbedder):
|
class OpenAIEmbedder(BaseEmbedder):
|
||||||
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
|
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
|
||||||
super().__init__(config=config)
|
super().__init__(config=config)
|
||||||
|
|
||||||
if self.config.model is None:
|
if self.config.model is None:
|
||||||
self.config.model = "text-embedding-ada-002"
|
self.config.model = "text-embedding-ada-002"
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def register_deserializable(cls: Type[T]) -> Type[T]:
|
|||||||
Returns:
|
Returns:
|
||||||
Type: The same class, after registration.
|
Type: The same class, after registration.
|
||||||
"""
|
"""
|
||||||
JSONSerializable.register_class_as_deserializable(cls)
|
JSONSerializable._register_class_as_deserializable(cls)
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ class JSONSerializable:
|
|||||||
return cls.deserialize(json_str)
|
return cls.deserialize(json_str)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_class_as_deserializable(cls, target_class: Type[T]) -> None:
|
def _register_class_as_deserializable(cls, target_class: Type[T]) -> None:
|
||||||
"""
|
"""
|
||||||
Register a class as deserializable. This is a classmethod and globally shared.
|
Register a class as deserializable. This is a classmethod and globally shared.
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class HuggingFaceLlm(BaseLlm):
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(
|
||||||
"The required dependencies for HuggingFaceHub are not installed."
|
"The required dependencies for HuggingFaceHub are not installed."
|
||||||
'Please install with `pip install --upgrade "embedchain[huggingface_hub]"`'
|
'Please install with `pip install --upgrade "embedchain[huggingface-hub]"`'
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
super().__init__(config=config)
|
super().__init__(config=config)
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
|
from embedchain.utils import clean_string
|
||||||
|
|
||||||
|
|
||||||
|
class DiscourseLoader(BaseLoader):
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
super().__init__()
|
||||||
|
if not config:
|
||||||
|
raise ValueError(
|
||||||
|
"DiscourseLoader requires a config. Check the documentation for the correct format - `https://docs.embedchain.ai/data-sources/discourse`" # noqa: E501
|
||||||
|
)
|
||||||
|
|
||||||
|
self.domain = config.get("domain")
|
||||||
|
if not self.domain:
|
||||||
|
raise ValueError(
|
||||||
|
"DiscourseLoader requires a domain. Check the documentation for the correct format - `https://docs.embedchain.ai/data-sources/discourse`" # noqa: E501
|
||||||
|
)
|
||||||
|
|
||||||
|
def _check_query(self, query):
|
||||||
|
if not query or not isinstance(query, str):
|
||||||
|
raise ValueError(
|
||||||
|
"DiscourseLoader requires a query. Check the documentation for the correct format - `https://docs.embedchain.ai/data-sources/discourse`" # noqa: E501
|
||||||
|
)
|
||||||
|
|
||||||
|
def _load_post(self, post_id):
|
||||||
|
post_url = f"{self.domain}posts/{post_id}.json"
|
||||||
|
response = requests.get(post_url)
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to load post {post_id}: {e}")
|
||||||
|
return
|
||||||
|
response_data = response.json()
|
||||||
|
post_contents = clean_string(response_data.get("raw"))
|
||||||
|
meta_data = {
|
||||||
|
"url": post_url,
|
||||||
|
"created_at": response_data.get("created_at", ""),
|
||||||
|
"username": response_data.get("username", ""),
|
||||||
|
"topic_slug": response_data.get("topic_slug", ""),
|
||||||
|
"score": response_data.get("score", ""),
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"content": post_contents,
|
||||||
|
"meta_data": meta_data,
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
def load_data(self, query):
|
||||||
|
self._check_query(query)
|
||||||
|
data = []
|
||||||
|
data_contents = []
|
||||||
|
logging.info(f"Searching data on discourse url: {self.domain}, for query: {query}")
|
||||||
|
search_url = f"{self.domain}search.json?q={query}"
|
||||||
|
response = requests.get(search_url)
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"Failed to search query {query}: {e}")
|
||||||
|
response_data = response.json()
|
||||||
|
post_ids = response_data.get("grouped_search_result").get("post_ids")
|
||||||
|
for id in post_ids:
|
||||||
|
post_data = self._load_post(id)
|
||||||
|
if post_data:
|
||||||
|
data.append(post_data)
|
||||||
|
data_contents.append(post_data.get("content"))
|
||||||
|
# Sleep for 0.4 sec, to avoid rate limiting. Check `https://meta.discourse.org/t/api-rate-limits/208405/6`
|
||||||
|
time.sleep(0.4)
|
||||||
|
doc_id = hashlib.sha256((query + ", ".join(data_contents)).encode()).hexdigest()
|
||||||
|
response_data = {"doc_id": doc_id, "data": data}
|
||||||
|
return response_data
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import concurrent.futures
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
|
from embedchain.loaders.json import JSONLoader
|
||||||
|
from embedchain.loaders.mdx import MdxLoader
|
||||||
|
from embedchain.utils import detect_datatype
|
||||||
|
|
||||||
|
|
||||||
|
def _load_file_data(path):
|
||||||
|
data = []
|
||||||
|
data_content = []
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
content = f.read().decode("utf-8")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading file {path}: {e}")
|
||||||
|
raise ValueError(f"Failed to read file {path}")
|
||||||
|
|
||||||
|
meta_data = {}
|
||||||
|
meta_data["url"] = path
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"content": content,
|
||||||
|
"meta_data": meta_data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
data_content.append(content)
|
||||||
|
doc_id = hashlib.sha256((" ".join(data_content) + path).encode()).hexdigest()
|
||||||
|
return {
|
||||||
|
"doc_id": doc_id,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GithubLoader(BaseLoader):
|
||||||
|
def load_data(self, repo_url):
|
||||||
|
"""Load data from a git repo."""
|
||||||
|
try:
|
||||||
|
from git import Repo
|
||||||
|
except ImportError as e:
|
||||||
|
raise ValueError(
|
||||||
|
"GithubLoader requires extra dependencies. Install with `pip install --upgrade 'embedchain[git]'`"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
mdx_loader = MdxLoader()
|
||||||
|
json_loader = JSONLoader()
|
||||||
|
data = []
|
||||||
|
data_urls = []
|
||||||
|
|
||||||
|
def _fetch_or_clone_repo(repo_url: str, local_path: str):
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
logging.info("Repository already exists. Fetching updates...")
|
||||||
|
repo = Repo(local_path)
|
||||||
|
origin = repo.remotes.origin
|
||||||
|
origin.fetch()
|
||||||
|
logging.info("Fetch completed.")
|
||||||
|
else:
|
||||||
|
logging.info("Cloning repository...")
|
||||||
|
Repo.clone_from(repo_url, local_path)
|
||||||
|
logging.info("Clone completed.")
|
||||||
|
|
||||||
|
def _load_file(file_path: str):
|
||||||
|
try:
|
||||||
|
data_type = detect_datatype(file_path).value
|
||||||
|
except Exception:
|
||||||
|
data_type = "unstructured"
|
||||||
|
|
||||||
|
if data_type == "mdx":
|
||||||
|
data = mdx_loader.load_data(file_path)
|
||||||
|
elif data_type == "json":
|
||||||
|
data = json_loader.load_data(file_path)
|
||||||
|
else:
|
||||||
|
data = _load_file_data(file_path)
|
||||||
|
|
||||||
|
return data.get("data", [])
|
||||||
|
|
||||||
|
def _is_file_empty(file_path):
|
||||||
|
return os.path.getsize(file_path) == 0
|
||||||
|
|
||||||
|
def _is_whitelisted(file_path):
|
||||||
|
whitelisted_extensions = ["md", "txt", "html", "json", "py", "js", "jsx", "ts", "tsx", "mdx", "rst"]
|
||||||
|
_, file_extension = os.path.splitext(file_path)
|
||||||
|
return file_extension[1:] in whitelisted_extensions
|
||||||
|
|
||||||
|
def _add_repo_files(repo_path: str):
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
||||||
|
future_to_file = {
|
||||||
|
executor.submit(_load_file, os.path.join(root, filename)): os.path.join(root, filename)
|
||||||
|
for root, _, files in os.walk(repo_path)
|
||||||
|
for filename in files
|
||||||
|
if _is_whitelisted(os.path.join(root, filename))
|
||||||
|
and not _is_file_empty(os.path.join(root, filename)) # noqa:E501
|
||||||
|
}
|
||||||
|
for future in tqdm(concurrent.futures.as_completed(future_to_file), total=len(future_to_file)):
|
||||||
|
file = future_to_file[future]
|
||||||
|
try:
|
||||||
|
results = future.result()
|
||||||
|
if results:
|
||||||
|
data.extend(results)
|
||||||
|
data_urls.extend([result.get("meta_data").get("url") for result in results])
|
||||||
|
except Exception as e:
|
||||||
|
logging.warn(f"Failed to process {file}: {e}")
|
||||||
|
|
||||||
|
source_hash = hashlib.sha256(repo_url.encode()).hexdigest()
|
||||||
|
repo_path = f"/tmp/{source_hash}"
|
||||||
|
_fetch_or_clone_repo(repo_url=repo_url, local_path=repo_path)
|
||||||
|
_add_repo_files(repo_path)
|
||||||
|
doc_id = hashlib.sha256((repo_url + ", ".join(data_urls)).encode()).hexdigest()
|
||||||
|
return {
|
||||||
|
"doc_id": doc_id,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
|
from embedchain.utils import clean_string
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLLoader(BaseLoader):
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]]):
|
||||||
|
super().__init__()
|
||||||
|
if not config:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid sql config: {config}.",
|
||||||
|
"Provide the correct config, refer `https://docs.embedchain.ai/data-sources/mysql`.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.connection = None
|
||||||
|
self.cursor = None
|
||||||
|
self._setup_loader(config=config)
|
||||||
|
|
||||||
|
def _setup_loader(self, config: Dict[str, Any]):
|
||||||
|
try:
|
||||||
|
import mysql.connector as sqlconnector
|
||||||
|
except ImportError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"Unable to import required packages for MySQL loader. Run `pip install --upgrade 'embedchain[mysql]'`." # noqa: E501
|
||||||
|
) from e
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.connection = sqlconnector.connection.MySQLConnection(**config)
|
||||||
|
self.cursor = self.connection.cursor()
|
||||||
|
except (sqlconnector.Error, IOError) as err:
|
||||||
|
logging.info(f"Connection failed: {err}")
|
||||||
|
raise ValueError(
|
||||||
|
f"Unable to connect with the given config: {config}.",
|
||||||
|
"Please provide the correct configuration to load data from you MySQL DB. \
|
||||||
|
Refer `https://docs.embedchain.ai/data-sources/mysql`.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _check_query(self, query):
|
||||||
|
if not isinstance(query, str):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid mysql query: {query}",
|
||||||
|
"Provide the valid query to add from mysql, \
|
||||||
|
make sure you are following `https://docs.embedchain.ai/data-sources/mysql`",
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_data(self, query):
|
||||||
|
self._check_query(query=query)
|
||||||
|
data = []
|
||||||
|
data_content = []
|
||||||
|
self.cursor.execute(query)
|
||||||
|
rows = self.cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
doc_content = clean_string(str(row))
|
||||||
|
data.append({"content": doc_content, "meta_data": {"url": query}})
|
||||||
|
data_content.append(doc_content)
|
||||||
|
doc_id = hashlib.sha256((query + ", ".join(data_content)).encode()).hexdigest()
|
||||||
|
return {
|
||||||
|
"doc_id": doc_id,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
@@ -40,9 +40,7 @@ class PostgresLoader(BaseLoader):
|
|||||||
def _check_query(self, query):
|
def _check_query(self, query):
|
||||||
if not isinstance(query, str):
|
if not isinstance(query, str):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid postgres query: {query}",
|
f"Invalid postgres query: {query}. Provide the valid source to add from postgres, make sure you are following `https://docs.embedchain.ai/data-sources/postgres`", # noqa:E501
|
||||||
"Provide the valid source to add from postgres, \
|
|
||||||
make sure you are following `https://docs.embedchain.ai/data-sources/postgres`",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def load_data(self, query):
|
def load_data(self, query):
|
||||||
@@ -54,7 +52,7 @@ class PostgresLoader(BaseLoader):
|
|||||||
results = self.cursor.fetchall()
|
results = self.cursor.fetchall()
|
||||||
for result in results:
|
for result in results:
|
||||||
doc_content = str(result)
|
doc_content = str(result)
|
||||||
data.append({"content": doc_content, "meta_data": {"url": f"postgres_query-({query})"}})
|
data.append({"content": doc_content, "meta_data": {"url": query}})
|
||||||
data_content.append(doc_content)
|
data_content.append(doc_content)
|
||||||
doc_id = hashlib.sha256((query + ", ".join(data_content)).encode()).hexdigest()
|
doc_id = hashlib.sha256((query + ", ".join(data_content)).encode()).hexdigest()
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import concurrent.futures
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
@@ -19,33 +21,45 @@ from embedchain.utils import is_readable
|
|||||||
|
|
||||||
@register_deserializable
|
@register_deserializable
|
||||||
class SitemapLoader(BaseLoader):
|
class SitemapLoader(BaseLoader):
|
||||||
|
"""
|
||||||
|
This method takes a sitemap URL as input and retrieves
|
||||||
|
all the URLs to use the WebPageLoader to load content
|
||||||
|
of each page.
|
||||||
|
"""
|
||||||
|
|
||||||
def load_data(self, sitemap_url):
|
def load_data(self, sitemap_url):
|
||||||
"""
|
|
||||||
This method takes a sitemap URL as input and retrieves
|
|
||||||
all the URLs to use the WebPageLoader to load content
|
|
||||||
of each page.
|
|
||||||
"""
|
|
||||||
output = []
|
output = []
|
||||||
web_page_loader = WebPageLoader()
|
web_page_loader = WebPageLoader()
|
||||||
response = requests.get(sitemap_url)
|
response = requests.get(sitemap_url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
soup = BeautifulSoup(response.text, "xml")
|
soup = BeautifulSoup(response.text, "xml")
|
||||||
|
|
||||||
links = [link.text for link in soup.find_all("loc") if link.parent.name == "url"]
|
links = [link.text for link in soup.find_all("loc") if link.parent.name == "url"]
|
||||||
if len(links) == 0:
|
if len(links) == 0:
|
||||||
# Get all <loc> tags as a fallback. This might include images.
|
|
||||||
links = [link.text for link in soup.find_all("loc")]
|
links = [link.text for link in soup.find_all("loc")]
|
||||||
|
|
||||||
doc_id = hashlib.sha256((" ".join(links) + sitemap_url).encode()).hexdigest()
|
doc_id = hashlib.sha256((" ".join(links) + sitemap_url).encode()).hexdigest()
|
||||||
|
|
||||||
for link in links:
|
def load_link(link):
|
||||||
try:
|
try:
|
||||||
each_load_data = web_page_loader.load_data(link)
|
each_load_data = web_page_loader.load_data(link)
|
||||||
if is_readable(each_load_data.get("data")[0].get("content")):
|
if is_readable(each_load_data.get("data")[0].get("content")):
|
||||||
output.append(each_load_data.get("data"))
|
return each_load_data.get("data")
|
||||||
else:
|
else:
|
||||||
logging.warning(f"Page is not readable (too many invalid characters): {link}")
|
logging.warning(f"Page is not readable (too many invalid characters): {link}")
|
||||||
except ParserRejectedMarkup as e:
|
except ParserRejectedMarkup as e:
|
||||||
logging.error(f"Failed to parse {link}: {e}")
|
logging.error(f"Failed to parse {link}: {e}")
|
||||||
return {"doc_id": doc_id, "data": [data[0] for data in output]}
|
return None
|
||||||
|
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
future_to_link = {executor.submit(load_link, link): link for link in links}
|
||||||
|
for future in tqdm(concurrent.futures.as_completed(future_to_link), total=len(links), desc="Loading pages"):
|
||||||
|
link = future_to_link[future]
|
||||||
|
try:
|
||||||
|
data = future.result()
|
||||||
|
if data:
|
||||||
|
output.extend(data)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error loading page {link}: {e}")
|
||||||
|
|
||||||
|
return {"doc_id": doc_id, "data": output}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import ssl
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import certifi
|
||||||
|
|
||||||
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
|
from embedchain.utils import clean_string
|
||||||
|
|
||||||
|
SLACK_API_BASE_URL = "https://www.slack.com/api/"
|
||||||
|
|
||||||
|
|
||||||
|
class SlackLoader(BaseLoader):
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
if config is not None:
|
||||||
|
self.config = config
|
||||||
|
else:
|
||||||
|
self.config = {"base_url": SLACK_API_BASE_URL}
|
||||||
|
|
||||||
|
self.client = None
|
||||||
|
self._setup_loader(self.config)
|
||||||
|
|
||||||
|
def _setup_loader(self, config: Dict[str, Any]):
|
||||||
|
try:
|
||||||
|
from slack_sdk import WebClient
|
||||||
|
except ImportError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"Slack loader requires extra dependencies. \
|
||||||
|
Install with `pip install --upgrade embedchain[slack]`"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
if os.getenv("SLACK_USER_TOKEN") is None:
|
||||||
|
raise ValueError(
|
||||||
|
"SLACK_USER_TOKEN environment variables not provided. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(f"Creating Slack Loader with config: {config}")
|
||||||
|
# get slack client config params
|
||||||
|
slack_bot_token = os.getenv("SLACK_USER_TOKEN")
|
||||||
|
ssl_cert = ssl.create_default_context(cafile=certifi.where())
|
||||||
|
base_url = config.get("base_url", SLACK_API_BASE_URL)
|
||||||
|
headers = config.get("headers")
|
||||||
|
# for Org-Wide App
|
||||||
|
team_id = config.get("team_id")
|
||||||
|
|
||||||
|
self.client = WebClient(
|
||||||
|
token=slack_bot_token,
|
||||||
|
base_url=base_url,
|
||||||
|
ssl=ssl_cert,
|
||||||
|
headers=headers,
|
||||||
|
team_id=team_id,
|
||||||
|
)
|
||||||
|
logging.info("Slack Loader setup successful!")
|
||||||
|
|
||||||
|
def _check_query(self, query):
|
||||||
|
if not isinstance(query, str):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid query passed to Slack loader, found: {query}. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_data(self, query):
|
||||||
|
self._check_query(query)
|
||||||
|
try:
|
||||||
|
data = []
|
||||||
|
data_content = []
|
||||||
|
|
||||||
|
logging.info(f"Searching slack conversations for query: {query}")
|
||||||
|
results = self.client.search_messages(
|
||||||
|
query=query,
|
||||||
|
sort="timestamp",
|
||||||
|
sort_dir="desc",
|
||||||
|
count=1000,
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = results.get("messages")
|
||||||
|
num_message = results.get("total")
|
||||||
|
logging.info(f"Found {num_message} messages for query: {query}")
|
||||||
|
|
||||||
|
matches = messages.get("matches", [])
|
||||||
|
for message in matches:
|
||||||
|
url = message.get("permalink")
|
||||||
|
text = message.get("text")
|
||||||
|
content = clean_string(text)
|
||||||
|
|
||||||
|
message_meta_data_keys = ["channel", "iid", "team", "ts", "type", "user", "username"]
|
||||||
|
meta_data = message.fromkeys(message_meta_data_keys, "")
|
||||||
|
meta_data.update({"url": url})
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"content": content,
|
||||||
|
"meta_data": meta_data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
data_content.append(content)
|
||||||
|
doc_id = hashlib.md5((query + ", ".join(data_content)).encode()).hexdigest()
|
||||||
|
return {
|
||||||
|
"doc_id": doc_id,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"Error in loading slack data: {e}")
|
||||||
|
raise ValueError(
|
||||||
|
f"Error in loading slack data: {e}. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501
|
||||||
|
) from e
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
|
from embedchain.utils import is_readable
|
||||||
|
|
||||||
|
|
||||||
|
@register_deserializable
|
||||||
|
class SubstackLoader(BaseLoader):
|
||||||
|
"""
|
||||||
|
This method takes a sitemap URL as input and retrieves
|
||||||
|
all the URLs to use the WebPageLoader to load content
|
||||||
|
of each page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def load_data(self, url: str):
|
||||||
|
try:
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from bs4.builder import ParserRejectedMarkup
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
'Substack requires extra dependencies. Install with `pip install --upgrade "embedchain[dataloaders]"`'
|
||||||
|
) from None
|
||||||
|
|
||||||
|
output = []
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.text, "xml")
|
||||||
|
links = [link.text for link in soup.find_all("loc") if link.parent.name == "url" and "/p/" in link.text]
|
||||||
|
if len(links) == 0:
|
||||||
|
links = [link.text for link in soup.find_all("loc") if "/p/" in link.text]
|
||||||
|
|
||||||
|
doc_id = hashlib.sha256((" ".join(links) + url).encode()).hexdigest()
|
||||||
|
|
||||||
|
def serialize_response(soup: BeautifulSoup):
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
h1_els = soup.find_all("h1")
|
||||||
|
if h1_els is not None and len(h1_els) > 0:
|
||||||
|
data["title"] = h1_els[1].text
|
||||||
|
|
||||||
|
description_el = soup.find("meta", {"name": "description"})
|
||||||
|
if description_el is not None:
|
||||||
|
data["description"] = description_el["content"]
|
||||||
|
|
||||||
|
content_el = soup.find("div", {"class": "available-content"})
|
||||||
|
if content_el is not None:
|
||||||
|
data["content"] = content_el.text
|
||||||
|
|
||||||
|
like_btn = soup.find("div", {"class": "like-button-container"})
|
||||||
|
if like_btn is not None:
|
||||||
|
no_of_likes_div = like_btn.find("div", {"class": "label"})
|
||||||
|
if no_of_likes_div is not None:
|
||||||
|
data["no_of_likes"] = no_of_likes_div.text
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def load_link(link: str):
|
||||||
|
try:
|
||||||
|
each_load_data = requests.get(link)
|
||||||
|
each_load_data.raise_for_status()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
|
data = serialize_response(soup)
|
||||||
|
data = str(data)
|
||||||
|
if is_readable(data):
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
logging.warning(f"Page is not readable (too many invalid characters): {link}")
|
||||||
|
except ParserRejectedMarkup as e:
|
||||||
|
logging.error(f"Failed to parse {link}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for link in links:
|
||||||
|
data = load_link(link)
|
||||||
|
if data:
|
||||||
|
output.append({"content": data, "meta_data": {"url": link}})
|
||||||
|
# TODO: allow users to configure this
|
||||||
|
time.sleep(1.0) # added to avoid rate limiting
|
||||||
|
|
||||||
|
return {"doc_id": doc_id, "data": output}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
try:
|
|
||||||
from langchain.document_loaders import UnstructuredFileLoader
|
|
||||||
except ImportError:
|
|
||||||
raise ImportError(
|
|
||||||
'PDF File requires extra dependencies. Install with `pip install --upgrade "embedchain[dataloaders]"`'
|
|
||||||
) from None
|
|
||||||
from embedchain.helper.json_serializable import register_deserializable
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
from embedchain.loaders.base_loader import BaseLoader
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
from embedchain.utils import clean_string
|
from embedchain.utils import clean_string
|
||||||
@@ -15,6 +9,13 @@ from embedchain.utils import clean_string
|
|||||||
class UnstructuredLoader(BaseLoader):
|
class UnstructuredLoader(BaseLoader):
|
||||||
def load_data(self, url):
|
def load_data(self, url):
|
||||||
"""Load data from a Unstructured file."""
|
"""Load data from a Unstructured file."""
|
||||||
|
try:
|
||||||
|
from langchain.document_loaders import UnstructuredFileLoader
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
'Unstructured file requires extra dependencies. Install with `pip install --upgrade "embedchain[dataloaders]"`' # noqa: E501
|
||||||
|
) from None
|
||||||
|
|
||||||
loader = UnstructuredFileLoader(url)
|
loader = UnstructuredFileLoader(url)
|
||||||
data = []
|
data = []
|
||||||
all_content = []
|
all_content = []
|
||||||
|
|||||||
@@ -17,15 +17,17 @@ from embedchain.utils import clean_string
|
|||||||
|
|
||||||
@register_deserializable
|
@register_deserializable
|
||||||
class WebPageLoader(BaseLoader):
|
class WebPageLoader(BaseLoader):
|
||||||
|
# Shared session for all instances
|
||||||
|
_session = requests.Session()
|
||||||
|
|
||||||
def load_data(self, url):
|
def load_data(self, url):
|
||||||
"""Load data from a web page."""
|
"""Load data from a web page using a shared requests session."""
|
||||||
response = requests.get(url)
|
response = self._session.get(url, timeout=30)
|
||||||
|
response.raise_for_status()
|
||||||
data = response.content
|
data = response.content
|
||||||
content = self._get_clean_content(data, url)
|
content = self._get_clean_content(data, url)
|
||||||
|
|
||||||
meta_data = {
|
meta_data = {"url": url}
|
||||||
"url": url,
|
|
||||||
}
|
|
||||||
|
|
||||||
doc_id = hashlib.sha256((content + url).encode()).hexdigest()
|
doc_id = hashlib.sha256((content + url).encode()).hexdigest()
|
||||||
return {
|
return {
|
||||||
@@ -86,3 +88,7 @@ class WebPageLoader(BaseLoader):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def close_session(cls):
|
||||||
|
cls._session.close()
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import concurrent.futures
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from embedchain.loaders.base_loader import BaseLoader
|
||||||
|
from embedchain.loaders.youtube_video import YoutubeVideoLoader
|
||||||
|
|
||||||
|
|
||||||
|
class YoutubeChannelLoader(BaseLoader):
|
||||||
|
"""Loader for youtube channel."""
|
||||||
|
|
||||||
|
def load_data(self, channel_name):
|
||||||
|
try:
|
||||||
|
import yt_dlp
|
||||||
|
except ImportError as e:
|
||||||
|
raise ValueError(
|
||||||
|
"YoutubeLoader requires extra dependencies. Install with `pip install --upgrade 'embedchain[youtube_channel]'`" # noqa: E501
|
||||||
|
) from e
|
||||||
|
|
||||||
|
data = []
|
||||||
|
data_urls = []
|
||||||
|
youtube_url = f"https://www.youtube.com/{channel_name}/videos"
|
||||||
|
youtube_video_loader = YoutubeVideoLoader()
|
||||||
|
|
||||||
|
def _get_yt_video_links():
|
||||||
|
try:
|
||||||
|
ydl_opts = {
|
||||||
|
"quiet": True,
|
||||||
|
"extract_flat": True,
|
||||||
|
}
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
info_dict = ydl.extract_info(youtube_url, download=False)
|
||||||
|
if "entries" in info_dict:
|
||||||
|
videos = [entry["url"] for entry in info_dict["entries"]]
|
||||||
|
return videos
|
||||||
|
except Exception:
|
||||||
|
logging.error(f"Failed to fetch youtube videos for channel: {channel_name}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _load_yt_video(video_link):
|
||||||
|
try:
|
||||||
|
each_load_data = youtube_video_loader.load_data(video_link)
|
||||||
|
if each_load_data:
|
||||||
|
return each_load_data.get("data")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to load youtube video {video_link}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _add_youtube_channel():
|
||||||
|
video_links = _get_yt_video_links()
|
||||||
|
logging.info("Loading videos from youtube channel...")
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
# Submitting all tasks and storing the future object with the video link
|
||||||
|
future_to_video = {
|
||||||
|
executor.submit(_load_yt_video, video_link): video_link for video_link in video_links
|
||||||
|
}
|
||||||
|
|
||||||
|
for future in tqdm(
|
||||||
|
concurrent.futures.as_completed(future_to_video), total=len(video_links), desc="Processing videos"
|
||||||
|
):
|
||||||
|
video = future_to_video[future]
|
||||||
|
try:
|
||||||
|
results = future.result()
|
||||||
|
if results:
|
||||||
|
data.extend(results)
|
||||||
|
data_urls.extend([result.get("meta_data").get("url") for result in results])
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to process youtube video {video}: {e}")
|
||||||
|
|
||||||
|
_add_youtube_channel()
|
||||||
|
doc_id = hashlib.sha256((youtube_url + ", ".join(data_urls)).encode()).hexdigest()
|
||||||
|
return {
|
||||||
|
"doc_id": doc_id,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ class YoutubeVideoLoader(BaseLoader):
|
|||||||
doc = loader.load()
|
doc = loader.load()
|
||||||
output = []
|
output = []
|
||||||
if not len(doc):
|
if not len(doc):
|
||||||
raise ValueError("No data found")
|
raise ValueError(f"No data found for url: {url}")
|
||||||
content = doc[0].page_content
|
content = doc[0].page_content
|
||||||
content = clean_string(content)
|
content = clean_string(content)
|
||||||
meta_data = doc[0].metadata
|
meta_data = doc[0].metadata
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ class IndirectDataType(Enum):
|
|||||||
OPENAPI = "openapi"
|
OPENAPI = "openapi"
|
||||||
GMAIL = "gmail"
|
GMAIL = "gmail"
|
||||||
POSTGRES = "postgres"
|
POSTGRES = "postgres"
|
||||||
|
MYSQL = "mysql"
|
||||||
|
SLACK = "slack"
|
||||||
|
DISCOURSE = "discourse"
|
||||||
|
SUBSTACK = "substack"
|
||||||
|
GITHUB = "github"
|
||||||
|
YOUTUBE_CHANNEL = "youtube_channel"
|
||||||
|
|
||||||
|
|
||||||
class SpecialDataType(Enum):
|
class SpecialDataType(Enum):
|
||||||
@@ -59,3 +65,9 @@ class DataType(Enum):
|
|||||||
OPENAPI = IndirectDataType.OPENAPI.value
|
OPENAPI = IndirectDataType.OPENAPI.value
|
||||||
GMAIL = IndirectDataType.GMAIL.value
|
GMAIL = IndirectDataType.GMAIL.value
|
||||||
POSTGRES = IndirectDataType.POSTGRES.value
|
POSTGRES = IndirectDataType.POSTGRES.value
|
||||||
|
MYSQL = IndirectDataType.MYSQL.value
|
||||||
|
SLACK = IndirectDataType.SLACK.value
|
||||||
|
DISCOURSE = IndirectDataType.DISCOURSE.value
|
||||||
|
SUBSTACK = IndirectDataType.SUBSTACK.value
|
||||||
|
GITHUB = IndirectDataType.GITHUB.value
|
||||||
|
YOUTUBE_CHANNEL = IndirectDataType.YOUTUBE_CHANNEL.value
|
||||||
|
|||||||
@@ -357,6 +357,9 @@ class Pipeline(EmbedChain):
|
|||||||
:return: An instance of the Pipeline class.
|
:return: An instance of the Pipeline class.
|
||||||
:rtype: Pipeline
|
:rtype: Pipeline
|
||||||
"""
|
"""
|
||||||
|
# Setup user directory if it doesn't exist already
|
||||||
|
Client.setup_dir()
|
||||||
|
|
||||||
with open(yaml_path, "r") as file:
|
with open(yaml_path, "r") as file:
|
||||||
config_data = yaml.safe_load(file)
|
config_data = yaml.safe_load(file)
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import os
|
|||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from openai.types.beta.threads import MessageContentText, ThreadMessage
|
from openai.types.beta.threads import MessageContentText, ThreadMessage
|
||||||
|
|
||||||
|
from embedchain import Pipeline
|
||||||
from embedchain.config import AddConfig
|
from embedchain.config import AddConfig
|
||||||
from embedchain.data_formatter import DataFormatter
|
from embedchain.data_formatter import DataFormatter
|
||||||
from embedchain.models.data_type import DataType
|
from embedchain.models.data_type import DataType
|
||||||
@@ -138,3 +140,65 @@ class OpenAIAssistant:
|
|||||||
with open(file_path, "wb") as file:
|
with open(file_path, "wb") as file:
|
||||||
file.write(data)
|
file.write(data)
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
|
class AIAssistant:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name=None,
|
||||||
|
instructions=None,
|
||||||
|
yaml_path=None,
|
||||||
|
assistant_id=None,
|
||||||
|
thread_id=None,
|
||||||
|
data_sources=None,
|
||||||
|
log_level=logging.WARN,
|
||||||
|
collect_metrics=True,
|
||||||
|
):
|
||||||
|
logging.basicConfig(level=log_level)
|
||||||
|
|
||||||
|
self.name = name or "AI Assistant"
|
||||||
|
self.data_sources = data_sources or []
|
||||||
|
self.log_level = log_level
|
||||||
|
self.instructions = instructions
|
||||||
|
self.assistant_id = assistant_id or str(uuid.uuid4())
|
||||||
|
self.thread_id = thread_id or str(uuid.uuid4())
|
||||||
|
self.pipeline = Pipeline.from_config(yaml_path=yaml_path) if yaml_path else Pipeline()
|
||||||
|
self.pipeline.local_id = self.pipeline.config.id = self.thread_id
|
||||||
|
|
||||||
|
if self.instructions:
|
||||||
|
self.pipeline.system_prompt = self.instructions
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"🎉 Created AI Assistant with name: {self.name}, assistant_id: {self.assistant_id}, thread_id: {self.thread_id}" # noqa: E501
|
||||||
|
)
|
||||||
|
|
||||||
|
# telemetry related properties
|
||||||
|
self._telemetry_props = {"class": self.__class__.__name__}
|
||||||
|
self.telemetry = AnonymousTelemetry(enabled=collect_metrics)
|
||||||
|
self.telemetry.capture(event_name="init", properties=self._telemetry_props)
|
||||||
|
|
||||||
|
if self.data_sources:
|
||||||
|
for data_source in self.data_sources:
|
||||||
|
metadata = {"assistant_id": self.assistant_id, "thread_id": "global_knowledge"}
|
||||||
|
self.pipeline.add(data_source["source"], data_source.get("data_type"), metadata=metadata)
|
||||||
|
|
||||||
|
def add(self, source, data_type=None):
|
||||||
|
metadata = {"assistant_id": self.assistant_id, "thread_id": self.thread_id}
|
||||||
|
self.pipeline.add(source, data_type=data_type, metadata=metadata)
|
||||||
|
event_props = {
|
||||||
|
**self._telemetry_props,
|
||||||
|
"data_type": data_type or detect_datatype(source),
|
||||||
|
}
|
||||||
|
self.telemetry.capture(event_name="add", properties=event_props)
|
||||||
|
|
||||||
|
def chat(self, query):
|
||||||
|
where = {
|
||||||
|
"$and": [
|
||||||
|
{"assistant_id": {"$eq": self.assistant_id}},
|
||||||
|
{"thread_id": {"$in": [self.thread_id, "global_knowledge"]}},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return self.pipeline.chat(query, where=where)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.pipeline.reset()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class AnonymousTelemetry:
|
|||||||
self.project_api_key = "phc_PHQDA5KwztijnSojsxJ2c1DuJd52QCzJzT2xnSGvjN2"
|
self.project_api_key = "phc_PHQDA5KwztijnSojsxJ2c1DuJd52QCzJzT2xnSGvjN2"
|
||||||
self.host = host
|
self.host = host
|
||||||
self.posthog = Posthog(project_api_key=self.project_api_key, host=self.host)
|
self.posthog = Posthog(project_api_key=self.project_api_key, host=self.host)
|
||||||
self.user_id = self.get_user_id()
|
self.user_id = self._get_user_id()
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
|
|
||||||
# Check if telemetry tracking is disabled via environment variable
|
# Check if telemetry tracking is disabled via environment variable
|
||||||
@@ -38,7 +38,7 @@ class AnonymousTelemetry:
|
|||||||
posthog_logger = logging.getLogger("posthog")
|
posthog_logger = logging.getLogger("posthog")
|
||||||
posthog_logger.disabled = True
|
posthog_logger.disabled = True
|
||||||
|
|
||||||
def get_user_id(self):
|
def _get_user_id(self):
|
||||||
if not os.path.exists(CONFIG_DIR):
|
if not os.path.exists(CONFIG_DIR):
|
||||||
os.makedirs(CONFIG_DIR)
|
os.makedirs(CONFIG_DIR)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,62 @@ from schema import Optional, Or, Schema
|
|||||||
from embedchain.models.data_type import DataType
|
from embedchain.models.data_type import DataType
|
||||||
|
|
||||||
|
|
||||||
|
def parse_content(content, type):
|
||||||
|
implemented = ["html.parser", "lxml", "lxml-xml", "xml", "html5lib"]
|
||||||
|
if type not in implemented:
|
||||||
|
raise ValueError(f"Parser type {type} not implemented. Please choose one of {implemented}")
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
soup = BeautifulSoup(content, type)
|
||||||
|
original_size = len(str(soup.get_text()))
|
||||||
|
|
||||||
|
tags_to_exclude = [
|
||||||
|
"nav",
|
||||||
|
"aside",
|
||||||
|
"form",
|
||||||
|
"header",
|
||||||
|
"noscript",
|
||||||
|
"svg",
|
||||||
|
"canvas",
|
||||||
|
"footer",
|
||||||
|
"script",
|
||||||
|
"style",
|
||||||
|
]
|
||||||
|
for tag in soup(tags_to_exclude):
|
||||||
|
tag.decompose()
|
||||||
|
|
||||||
|
ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"]
|
||||||
|
for id in ids_to_exclude:
|
||||||
|
tags = soup.find_all(id=id)
|
||||||
|
for tag in tags:
|
||||||
|
tag.decompose()
|
||||||
|
|
||||||
|
classes_to_exclude = [
|
||||||
|
"elementor-location-header",
|
||||||
|
"navbar-header",
|
||||||
|
"nav",
|
||||||
|
"header-sidebar-wrapper",
|
||||||
|
"blog-sidebar-wrapper",
|
||||||
|
"related-posts",
|
||||||
|
]
|
||||||
|
for class_name in classes_to_exclude:
|
||||||
|
tags = soup.find_all(class_=class_name)
|
||||||
|
for tag in tags:
|
||||||
|
tag.decompose()
|
||||||
|
|
||||||
|
content = soup.get_text()
|
||||||
|
content = clean_string(content)
|
||||||
|
|
||||||
|
cleaned_size = len(content)
|
||||||
|
if original_size != 0:
|
||||||
|
logging.info(
|
||||||
|
f"Cleaned page size: {cleaned_size} characters, down from {original_size} (shrunk: {original_size-cleaned_size} chars, {round((1-(cleaned_size/original_size)) * 100, 2)}%)" # noqa:E501
|
||||||
|
)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
def clean_string(text):
|
def clean_string(text):
|
||||||
"""
|
"""
|
||||||
This function takes in a string and performs a series of text cleaning operations.
|
This function takes in a string and performs a series of text cleaning operations.
|
||||||
@@ -161,6 +217,10 @@ def detect_datatype(source: Any) -> DataType:
|
|||||||
logging.debug(f"Source of `{formatted_source}` detected as `csv`.")
|
logging.debug(f"Source of `{formatted_source}` detected as `csv`.")
|
||||||
return DataType.CSV
|
return DataType.CSV
|
||||||
|
|
||||||
|
if url.path.endswith(".mdx") or url.path.endswith(".md"):
|
||||||
|
logging.debug(f"Source of `{formatted_source}` detected as `mdx`.")
|
||||||
|
return DataType.MDX
|
||||||
|
|
||||||
if url.path.endswith(".docx"):
|
if url.path.endswith(".docx"):
|
||||||
logging.debug(f"Source of `{formatted_source}` detected as `docx`.")
|
logging.debug(f"Source of `{formatted_source}` detected as `docx`.")
|
||||||
return DataType.DOCX
|
return DataType.DOCX
|
||||||
@@ -200,6 +260,10 @@ def detect_datatype(source: Any) -> DataType:
|
|||||||
logging.debug(f"Source of `{formatted_source}` detected as `docs_site`.")
|
logging.debug(f"Source of `{formatted_source}` detected as `docs_site`.")
|
||||||
return DataType.DOCS_SITE
|
return DataType.DOCS_SITE
|
||||||
|
|
||||||
|
if "github.com" in url.netloc:
|
||||||
|
logging.debug(f"Source of `{formatted_source}` detected as `github`.")
|
||||||
|
return DataType.GITHUB
|
||||||
|
|
||||||
# If none of the above conditions are met, it's a general web page
|
# 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`.")
|
logging.debug(f"Source of `{formatted_source}` detected as `web_page`.")
|
||||||
return DataType.WEB_PAGE
|
return DataType.WEB_PAGE
|
||||||
@@ -233,6 +297,10 @@ def detect_datatype(source: Any) -> DataType:
|
|||||||
logging.debug(f"Source of `{formatted_source}` detected as `xml`.")
|
logging.debug(f"Source of `{formatted_source}` detected as `xml`.")
|
||||||
return DataType.XML
|
return DataType.XML
|
||||||
|
|
||||||
|
if source.endswith(".mdx") or source.endswith(".md"):
|
||||||
|
logging.debug(f"Source of `{formatted_source}` detected as `mdx`.")
|
||||||
|
return DataType.MDX
|
||||||
|
|
||||||
if source.endswith(".yaml"):
|
if source.endswith(".yaml"):
|
||||||
with open(source, "r") as file:
|
with open(source, "r") as file:
|
||||||
yaml_content = yaml.safe_load(file)
|
yaml_content = yaml.safe_load(file)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
|
|||||||
|
|
||||||
from chromadb import Collection, QueryResult
|
from chromadb import Collection, QueryResult
|
||||||
from langchain.docstore.document import Document
|
from langchain.docstore.document import Document
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
from embedchain.config import ChromaDbConfig
|
from embedchain.config import ChromaDbConfig
|
||||||
from embedchain.helper.json_serializable import register_deserializable
|
from embedchain.helper.json_serializable import register_deserializable
|
||||||
@@ -157,8 +158,7 @@ class ChromaDB(BaseVectorDB):
|
|||||||
" Ids size: {}".format(len(documents), len(metadatas), len(ids))
|
" Ids size: {}".format(len(documents), len(metadatas), len(ids))
|
||||||
)
|
)
|
||||||
|
|
||||||
for i in range(0, len(documents), self.BATCH_SIZE):
|
for i in tqdm(range(0, len(documents), self.BATCH_SIZE), desc="Inserting batches in chromadb"):
|
||||||
print("Inserting batches from {} to {} in chromadb".format(i, min(len(documents), i + self.BATCH_SIZE)))
|
|
||||||
if skip_embedding:
|
if skip_embedding:
|
||||||
self.collection.add(
|
self.collection.add(
|
||||||
embeddings=embeddings[i : i + self.BATCH_SIZE],
|
embeddings=embeddings[i : i + self.BATCH_SIZE],
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from typing import Dict, List, Optional, Set, Tuple, Union
|
from typing import Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from opensearchpy import OpenSearch
|
from opensearchpy import OpenSearch
|
||||||
from opensearchpy.helpers import bulk
|
from opensearchpy.helpers import bulk
|
||||||
@@ -23,6 +26,8 @@ class OpenSearchDB(BaseVectorDB):
|
|||||||
OpenSearch as vector database
|
OpenSearch as vector database
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
BATCH_SIZE = 100
|
||||||
|
|
||||||
def __init__(self, config: OpenSearchDBConfig):
|
def __init__(self, config: OpenSearchDBConfig):
|
||||||
"""OpenSearch as vector database.
|
"""OpenSearch as vector database.
|
||||||
|
|
||||||
@@ -131,19 +136,28 @@ class OpenSearchDB(BaseVectorDB):
|
|||||||
:type skip_embedding: bool
|
:type skip_embedding: bool
|
||||||
"""
|
"""
|
||||||
|
|
||||||
docs = []
|
for i in tqdm(range(0, len(documents), self.BATCH_SIZE), desc="Inserting batches in opensearch"):
|
||||||
if not skip_embedding:
|
if not skip_embedding:
|
||||||
embeddings = self.embedder.embedding_fn(documents)
|
embeddings = self.embedder.embedding_fn(documents[i : i + self.BATCH_SIZE])
|
||||||
for id, text, metadata, embeddings in zip(ids, documents, metadatas, embeddings):
|
|
||||||
docs.append(
|
docs = []
|
||||||
{
|
for id, text, metadata, embeddings in zip(
|
||||||
"_index": self._get_index(),
|
ids[i : i + self.BATCH_SIZE],
|
||||||
"_id": id,
|
documents[i : i + self.BATCH_SIZE],
|
||||||
"_source": {"text": text, "metadata": metadata, "embeddings": embeddings},
|
metadatas[i : i + self.BATCH_SIZE],
|
||||||
}
|
embeddings[i : i + self.BATCH_SIZE],
|
||||||
)
|
):
|
||||||
bulk(self.client, docs)
|
docs.append(
|
||||||
self.client.indices.refresh(index=self._get_index())
|
{
|
||||||
|
"_index": self._get_index(),
|
||||||
|
"_id": id,
|
||||||
|
"_source": {"text": text, "metadata": metadata, "embeddings": embeddings},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bulk(self.client, docs)
|
||||||
|
self.client.indices.refresh(index=self._get_index())
|
||||||
|
# Sleep for 0.1 seconds to avoid rate limiting
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
def query(
|
def query(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ async def create_app_using_default_config(app_id: str, config: UploadFile = None
|
|||||||
|
|
||||||
return DefaultResponse(response=f"App created successfully. App ID: {app_id}")
|
return DefaultResponse(response=f"App created successfully. App ID: {app_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warn(str(e))
|
logging.warning(str(e))
|
||||||
raise HTTPException(detail=f"Error creating app: {str(e)}", status_code=400)
|
raise HTTPException(detail=f"Error creating app: {str(e)}", status_code=400)
|
||||||
|
|
||||||
|
|
||||||
@@ -113,13 +113,13 @@ async def get_datasources_associated_with_app_id(app_id: str, db: Session = Depe
|
|||||||
response = app.get_data_sources()
|
response = app.get_data_sources()
|
||||||
return {"results": response}
|
return {"results": response}
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
logging.warn(str(ve))
|
logging.warning(str(ve))
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail=generate_error_message_for_api_keys(ve),
|
detail=generate_error_message_for_api_keys(ve),
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warn(str(e))
|
logging.warning(str(e))
|
||||||
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
||||||
|
|
||||||
|
|
||||||
@@ -152,13 +152,13 @@ async def add_datasource_to_an_app(body: SourceApp, app_id: str, db: Session = D
|
|||||||
response = app.add(source=body.source, data_type=body.data_type)
|
response = app.add(source=body.source, data_type=body.data_type)
|
||||||
return DefaultResponse(response=response)
|
return DefaultResponse(response=response)
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
logging.warn(str(ve))
|
logging.warning(str(ve))
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail=generate_error_message_for_api_keys(ve),
|
detail=generate_error_message_for_api_keys(ve),
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warn(str(e))
|
logging.warning(str(e))
|
||||||
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
||||||
|
|
||||||
|
|
||||||
@@ -190,13 +190,13 @@ async def query_an_app(body: QueryApp, app_id: str, db: Session = Depends(get_db
|
|||||||
response = app.query(body.query)
|
response = app.query(body.query)
|
||||||
return DefaultResponse(response=response)
|
return DefaultResponse(response=response)
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
logging.warn(str(ve))
|
logging.warning(str(ve))
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail=generate_error_message_for_api_keys(ve),
|
detail=generate_error_message_for_api_keys(ve),
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warn(str(e))
|
logging.warning(str(e))
|
||||||
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
||||||
|
|
||||||
|
|
||||||
@@ -273,13 +273,13 @@ async def deploy_app(body: DeployAppRequest, app_id: str, db: Session = Depends(
|
|||||||
app.deploy()
|
app.deploy()
|
||||||
return DefaultResponse(response="App deployed successfully.")
|
return DefaultResponse(response="App deployed successfully.")
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
logging.warn(str(ve))
|
logging.warning(str(ve))
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail=generate_error_message_for_api_keys(ve),
|
detail=generate_error_message_for_api_keys(ve),
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warn(str(e))
|
logging.warning(str(e))
|
||||||
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /usr/src/
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["python", "-m", "embedchain.bots.slack", "--port", "8000"]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
embedchain[slack, poe]==0.1.7
|
||||||
Generated
+207
-60
@@ -744,13 +744,13 @@ numpy = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chromadb"
|
name = "chromadb"
|
||||||
version = "0.4.16"
|
version = "0.4.17"
|
||||||
description = "Chroma."
|
description = "Chroma."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "chromadb-0.4.16-py3-none-any.whl", hash = "sha256:a2e79d80cf25adc5658af568c66949628a8991779d832044a0fabed983b79fc3"},
|
{file = "chromadb-0.4.17-py3-none-any.whl", hash = "sha256:8cb88162bc6124441ba5a4b93819463a10e9aaafbe05a3286e876cbdc7a7e11d"},
|
||||||
{file = "chromadb-0.4.16.tar.gz", hash = "sha256:d5fb113ea02f87b969887279aec625e1a2a68bf6acedf1609f95d27670a78dc0"},
|
{file = "chromadb-0.4.17.tar.gz", hash = "sha256:120f9d364719b664d5314500f8e6097f0e0b24496bb97a429bc324f8d11f1b52"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1671,6 +1671,37 @@ files = [
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
wcwidth = ">=0.2.5"
|
wcwidth = ">=0.2.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitdb"
|
||||||
|
version = "4.0.11"
|
||||||
|
description = "Git Object Database"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
|
||||||
|
{file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
smmap = ">=3.0.1,<6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitpython"
|
||||||
|
version = "3.1.40"
|
||||||
|
description = "GitPython is a Python library used to interact with Git repositories"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"},
|
||||||
|
{file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
gitdb = ">=4.0.1,<5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "google-api-core"
|
name = "google-api-core"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
@@ -2742,13 +2773,13 @@ adal = ["adal (>=1.0.2)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain"
|
name = "langchain"
|
||||||
version = "0.0.332"
|
version = "0.0.336"
|
||||||
description = "Building applications with LLMs through composability"
|
description = "Building applications with LLMs through composability"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.1,<4.0"
|
python-versions = ">=3.8.1,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain-0.0.332-py3-none-any.whl", hash = "sha256:4cbf183b8a385483907192efea2f55d34c0f0c441b0a02f41af1eeec4526677c"},
|
{file = "langchain-0.0.336-py3-none-any.whl", hash = "sha256:cbc72c170c5eb67509bf44fb833412a3d4ccf4476136447abd4f10468ef7d4c4"},
|
||||||
{file = "langchain-0.0.332.tar.gz", hash = "sha256:8356b6c0073680d66d5ee2d9e54c23c90198ee74ab2431a0256934a69a511c1f"},
|
{file = "langchain-0.0.336.tar.gz", hash = "sha256:2cbb992b0a6975948d35616386d088c2920b66023cb94eb4f4b25e097fa1374d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2757,7 +2788,7 @@ anyio = "<4.0"
|
|||||||
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
|
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
|
||||||
dataclasses-json = ">=0.5.7,<0.7"
|
dataclasses-json = ">=0.5.7,<0.7"
|
||||||
jsonpatch = ">=1.33,<2.0"
|
jsonpatch = ">=1.33,<2.0"
|
||||||
langsmith = ">=0.0.52,<0.1.0"
|
langsmith = ">=0.0.63,<0.1.0"
|
||||||
numpy = ">=1,<2"
|
numpy = ">=1,<2"
|
||||||
pydantic = ">=1,<3"
|
pydantic = ">=1,<3"
|
||||||
PyYAML = ">=5.3"
|
PyYAML = ">=5.3"
|
||||||
@@ -2766,17 +2797,17 @@ SQLAlchemy = ">=1.4,<3"
|
|||||||
tenacity = ">=8.1.0,<9.0.0"
|
tenacity = ">=8.1.0,<9.0.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.8.3,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.10.1,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<4)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.6.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
|
all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.8.3,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.13.0,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<4)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.6.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
|
||||||
azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (>=0,<1)"]
|
azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (<2)"]
|
||||||
clarifai = ["clarifai (>=9.1.0)"]
|
clarifai = ["clarifai (>=9.1.0)"]
|
||||||
cli = ["typer (>=0.9.0,<0.10.0)"]
|
cli = ["typer (>=0.9.0,<0.10.0)"]
|
||||||
cohere = ["cohere (>=4,<5)"]
|
cohere = ["cohere (>=4,<5)"]
|
||||||
docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
|
docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
|
||||||
embeddings = ["sentence-transformers (>=2,<3)"]
|
embeddings = ["sentence-transformers (>=2,<3)"]
|
||||||
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.6.0,<0.7.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
|
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.6.0,<0.7.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
|
||||||
javascript = ["esprima (>=4.0.1,<5.0.0)"]
|
javascript = ["esprima (>=4.0.1,<5.0.0)"]
|
||||||
llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
|
llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
|
||||||
openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.6.0)"]
|
openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0)"]
|
||||||
qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
|
qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
|
||||||
text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
|
text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
|
||||||
|
|
||||||
@@ -2796,18 +2827,17 @@ six = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langsmith"
|
name = "langsmith"
|
||||||
version = "0.0.62"
|
version = "0.0.65"
|
||||||
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.1,<4.0"
|
python-versions = ">=3.8.1,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "langsmith-0.0.62-py3-none-any.whl", hash = "sha256:d50fe00eee06001ac5f705d4ff09c6b4a2f20688d57de5fbd6974a510da672fa"},
|
{file = "langsmith-0.0.65-py3-none-any.whl", hash = "sha256:92450957d1c6b6be814f9b726f3bc751deca684535fb404508ccad7aec1bb049"},
|
||||||
{file = "langsmith-0.0.62.tar.gz", hash = "sha256:196b16bea6856a83c8d95f3f709beebc6c72ea82c113021d3f62ac7cbde51bfe"},
|
{file = "langsmith-0.0.65.tar.gz", hash = "sha256:ef20e2e32392fb1a0fc5d171e8de595d868b4153a10cc119d7bf8418192c06b6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pydantic = ">=1,<3"
|
pydantic = ">=1,<3"
|
||||||
pytest-subtests = ">=0.11.0,<0.12.0"
|
|
||||||
requests = ">=2,<3"
|
requests = ">=2,<3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3351,6 +3381,17 @@ files = [
|
|||||||
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
|
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mutagen"
|
||||||
|
version = "1.47.0"
|
||||||
|
description = "read and write audio tags for many formats"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719"},
|
||||||
|
{file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -3362,6 +3403,50 @@ files = [
|
|||||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mysql-connector-python"
|
||||||
|
version = "8.2.0"
|
||||||
|
description = "MySQL driver written in Python"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "mysql-connector-python-8.2.0.tar.gz", hash = "sha256:884eba07b4c97edf552a03f5fdca145e0ab4afc3d8677cca20276effca1bea54"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:37ca26d7b10580b836f038d42f21ba9e6c88542868d50f55defdbd2dc8e0c0e6"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:9d598cc854d1b61eabad7cbf003cfe59970aae80384f5ff18a5cc3fba7becdcc"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:4828a08b738174cacb0985df01120e0a2f0ba534c9d2f67d6613b0930a0fe3cd"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp310-cp310-manylinux_2_17_x86_64.whl", hash = "sha256:4b2de9dd56de4874c30364023b59991399222cf73ec744da590cf9eef2623c26"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:858490bb450b6ae45f415d2205d65a12e84e3445c7b9736e1d1552b685bf237a"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5d1e1399a9feb45fc8caca6168ff35d31a8124693f24391153764bccc61d15ad"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:a0308455462d4078baf516255662a46611eb12fc8d83d40dea38df3032d2566d"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:539300944c36566e91d131e106dbf0a90cde697ba88247a820f5af9caea2e5c2"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp311-cp311-manylinux_2_17_x86_64.whl", hash = "sha256:66d755b94f547d6fcdee9f2256805a4534103363e35d185d3800bfc5274e1f4f"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:877076f2d71d268fb1b334c85a20ef1d42096ceb6580b25229be4510ebf5a0c5"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b3f64b1fd2de2e8ed5c9ddd388efce8c6804a8633e18dbb45563a6ae61fbc45b"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:e9eacdc3fa5f61276c1549ca7c471a6fde576692881a70dcbac8258314015347"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:230a34df2f3c4f36acf426361914b8552f129538a5e2256d489dca2b39f2f031"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp312-cp312-manylinux_2_17_x86_64.whl", hash = "sha256:26e19a4469276870ccc0a04db30c534519f0f774a5949370ff0dc03e7cfc071c"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:e0b8af0d8c56619875dee4168b1bf77e17a4c1c5e1df935623d264729df70227"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:81ac2e409b604bcf2ae6e18bc477f8f6e509ea5004c8dba291afe3d2591f0a3f"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:98606e893bc2343ccc9254f248e4bd5bae18cb03bf4931f3b1657900cd647718"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp38-cp38-manylinux_2_17_x86_64.whl", hash = "sha256:e33677bcf6c2bdee7f25f3b38da7a204ab761e3b6763cbb2a413a5300c151059"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:55a58e57824b03f31befdd460b1dd173a05605bfac25278cd9845f3927c94399"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:86e30370b6c38a9aaeae042eade5eea95000f6eb93b3802a8a215750a29a48c1"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:6ac2c0d9c0248df8a62a736ae9024e1934acdbdf9ce3b2ab57b2a99c1da5f028"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:8c3ed071e19981e8e4ae64c1e3ded050571637a8d519669c03be5e1029c04ef7"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp39-cp39-manylinux_2_17_x86_64.whl", hash = "sha256:717472cab0c4d5000cf60797be4d453b60d9ec98ec446c1e3c399fdd43941cb9"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:df033ca9c76f3a7c3500baea109127bc872c79431e9de691ffce4c2878af2828"},
|
||||||
|
{file = "mysql_connector_python-8.2.0-py2.py3-none-any.whl", hash = "sha256:59d4ea8253edbca7cbd1ac25ed524fcf5d8e34ee7ef5fb1be9e3026852b88126"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
protobuf = ">=4.21.1,<=4.21.12"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
compression = ["lz4 (>=2.1.6,<=4.3.2)", "zstandard (>=0.12.0,<=0.19.0)"]
|
||||||
|
dns-srv = ["dnspython (>=1.16.0,<=2.3.0)"]
|
||||||
|
gssapi = ["gssapi (>=1.6.9,<=1.8.2)"]
|
||||||
|
opentelemetry = ["Deprecated (>=1.2.6)", "typing-extensions (>=3.7.4)", "zipp (>=0.5)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nest-asyncio"
|
name = "nest-asyncio"
|
||||||
version = "1.5.8"
|
version = "1.5.8"
|
||||||
@@ -4292,24 +4377,25 @@ testing = ["google-api-core[grpc] (>=1.31.5)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf"
|
name = "protobuf"
|
||||||
version = "4.24.4"
|
version = "4.21.12"
|
||||||
description = ""
|
description = ""
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"},
|
{file = "protobuf-4.21.12-cp310-abi3-win32.whl", hash = "sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1"},
|
||||||
{file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"},
|
{file = "protobuf-4.21.12-cp310-abi3-win_amd64.whl", hash = "sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2"},
|
||||||
{file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"},
|
{file = "protobuf-4.21.12-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791"},
|
||||||
{file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"},
|
{file = "protobuf-4.21.12-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97"},
|
||||||
{file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"},
|
{file = "protobuf-4.21.12-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7"},
|
||||||
{file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"},
|
{file = "protobuf-4.21.12-cp37-cp37m-win32.whl", hash = "sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717"},
|
||||||
{file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"},
|
{file = "protobuf-4.21.12-cp37-cp37m-win_amd64.whl", hash = "sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"},
|
||||||
{file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"},
|
{file = "protobuf-4.21.12-cp38-cp38-win32.whl", hash = "sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec"},
|
||||||
{file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"},
|
{file = "protobuf-4.21.12-cp38-cp38-win_amd64.whl", hash = "sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30"},
|
||||||
{file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"},
|
{file = "protobuf-4.21.12-cp39-cp39-win32.whl", hash = "sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc"},
|
||||||
{file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"},
|
{file = "protobuf-4.21.12-cp39-cp39-win_amd64.whl", hash = "sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b"},
|
||||||
{file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"},
|
{file = "protobuf-4.21.12-py2.py3-none-any.whl", hash = "sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5"},
|
||||||
{file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"},
|
{file = "protobuf-4.21.12-py3-none-any.whl", hash = "sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462"},
|
||||||
|
{file = "protobuf-4.21.12.tar.gz", hash = "sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4565,6 +4651,47 @@ files = [
|
|||||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycryptodomex"
|
||||||
|
version = "3.19.0"
|
||||||
|
description = "Cryptographic library for Python"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
files = [
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff64fd720def623bf64d8776f8d0deada1cc1bf1ec3c1f9d6f5bb5bd098d034f"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:61056a1fd3254f6f863de94c233b30dd33bc02f8c935b2000269705f1eeeffa4"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:258c4233a3fe5a6341780306a36c6fb072ef38ce676a6d41eec3e591347919e8"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e45bb4635b3c4e0a00ca9df75ef6295838c85c2ac44ad882410cb631ed1eeaa"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a12144d785518f6491ad334c75ccdc6ad52ea49230b4237f319dbb7cef26f464"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:1789d89f61f70a4cd5483d4dfa8df7032efab1118f8b9894faae03c967707865"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:eb2fc0ec241bf5e5ef56c8fbec4a2634d631e4c4f616a59b567947a0f35ad83c"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c9a68a2f7bd091ccea54ad3be3e9d65eded813e6d79fdf4cc3604e26cdd6384f"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8df69e41f7e7015a90b94d1096ec3d8e0182e73449487306709ec27379fff761"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:917033016ecc23c8933205585a0ab73e20020fdf671b7cd1be788a5c4039840b"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:e8e5ecbd4da4157889fce8ba49da74764dd86c891410bfd6b24969fa46edda51"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-win32.whl", hash = "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002"},
|
||||||
|
{file = "pycryptodomex-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40"},
|
||||||
|
{file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644"},
|
||||||
|
{file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.4.2"
|
version = "2.4.2"
|
||||||
@@ -4934,21 +5061,6 @@ pytest = ">=5.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["pre-commit", "pytest-asyncio", "tox"]
|
dev = ["pre-commit", "pytest-asyncio", "tox"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytest-subtests"
|
|
||||||
version = "0.11.0"
|
|
||||||
description = "unittest subTest() support and subtests fixture"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "pytest-subtests-0.11.0.tar.gz", hash = "sha256:51865c88457545f51fb72011942f0a3c6901ee9e24cbfb6d1b9dc1348bafbe37"},
|
|
||||||
{file = "pytest_subtests-0.11.0-py3-none-any.whl", hash = "sha256:453389984952eec85ab0ce0c4f026337153df79587048271c7fd0f49119c07e4"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
attrs = ">=19.2.0"
|
|
||||||
pytest = ">=7.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.2"
|
version = "2.8.2"
|
||||||
@@ -5925,6 +6037,17 @@ files = [
|
|||||||
optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"]
|
optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"]
|
||||||
testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.8.0)", "boto3 (<=2)", "click (==8.0.4)", "databases (>=0.5)", "flake8 (>=5,<6)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"]
|
testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.8.0)", "boto3 (<=2)", "click (==8.0.4)", "databases (>=0.5)", "flake8 (>=5,<6)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smmap"
|
||||||
|
version = "5.0.1"
|
||||||
|
description = "A pure Python implementation of a sliding window memory map manager"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
|
||||||
|
{file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -6668,30 +6791,30 @@ backoff = "*"
|
|||||||
beautifulsoup4 = "*"
|
beautifulsoup4 = "*"
|
||||||
chardet = "*"
|
chardet = "*"
|
||||||
dataclasses-json = "*"
|
dataclasses-json = "*"
|
||||||
ebooklib = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
ebooklib = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
emoji = "*"
|
emoji = "*"
|
||||||
filetype = "*"
|
filetype = "*"
|
||||||
langdetect = "*"
|
langdetect = "*"
|
||||||
lxml = "*"
|
lxml = "*"
|
||||||
markdown = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
markdown = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
msg-parser = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
msg-parser = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
nltk = "*"
|
nltk = "*"
|
||||||
numpy = "*"
|
numpy = "*"
|
||||||
openpyxl = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
openpyxl = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
pandas = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
pandas = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
pdf2image = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
pdf2image = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
"pdfminer.six" = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
"pdfminer.six" = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
pypandoc = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
pypandoc = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
python-docx = {version = ">=1.0.1", optional = true, markers = "extra == \"local-inference\""}
|
python-docx = {version = ">=1.0.1", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
python-iso639 = "*"
|
python-iso639 = "*"
|
||||||
python-magic = "*"
|
python-magic = "*"
|
||||||
python-pptx = {version = "<=0.6.21", optional = true, markers = "extra == \"local-inference\""}
|
python-pptx = {version = "<=0.6.21", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
rapidfuzz = "*"
|
rapidfuzz = "*"
|
||||||
requests = "*"
|
requests = "*"
|
||||||
tabulate = "*"
|
tabulate = "*"
|
||||||
unstructured-inference = {version = "0.7.3", optional = true, markers = "extra == \"local-inference\""}
|
unstructured-inference = {version = "0.7.3", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
"unstructured.pytesseract" = {version = ">=0.3.12", optional = true, markers = "extra == \"local-inference\""}
|
"unstructured.pytesseract" = {version = ">=0.3.12", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
xlrd = {version = "*", optional = true, markers = "extra == \"local-inference\""}
|
xlrd = {version = "*", optional = true, markers = "extra == \"all-docs\" or extra == \"local-inference\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
airtable = ["pyairtable"]
|
airtable = ["pyairtable"]
|
||||||
@@ -7378,6 +7501,27 @@ files = [
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
requests = "*"
|
requests = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yt-dlp"
|
||||||
|
version = "2023.11.16"
|
||||||
|
description = "A youtube-dl fork with additional features and patches"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "yt-dlp-2023.11.16.tar.gz", hash = "sha256:f0ccdaf12e08b15902601a4671c7ab12906d7b11de3ae75fa6506811c24ec5da"},
|
||||||
|
{file = "yt_dlp-2023.11.16-py2.py3-none-any.whl", hash = "sha256:0322ba85aa4afdb75f8641ed550e5958964daff034aeb477abb15031fd9a51ed"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
brotli = {version = "*", markers = "implementation_name == \"cpython\""}
|
||||||
|
brotlicffi = {version = "*", markers = "implementation_name != \"cpython\""}
|
||||||
|
certifi = "*"
|
||||||
|
mutagen = "*"
|
||||||
|
pycryptodomex = "*"
|
||||||
|
requests = ">=2.31.0,<3"
|
||||||
|
urllib3 = ">=1.26.17,<3"
|
||||||
|
websockets = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zipp"
|
name = "zipp"
|
||||||
version = "3.17.0"
|
version = "3.17.0"
|
||||||
@@ -7396,15 +7540,17 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
|||||||
[extras]
|
[extras]
|
||||||
cohere = ["cohere"]
|
cohere = ["cohere"]
|
||||||
community = ["llama-hub"]
|
community = ["llama-hub"]
|
||||||
dataloaders = ["beautifulsoup4", "docx2txt", "duckduckgo-search", "pypdf", "pytube", "sentence-transformers", "unstructured"]
|
dataloaders = ["beautifulsoup4", "docx2txt", "duckduckgo-search", "pypdf", "pytube", "sentence-transformers", "unstructured", "youtube-transcript-api"]
|
||||||
discord = ["discord"]
|
discord = ["discord"]
|
||||||
elasticsearch = ["elasticsearch"]
|
elasticsearch = ["elasticsearch"]
|
||||||
|
git = ["gitpython"]
|
||||||
gmail = ["llama-hub", "requests"]
|
gmail = ["llama-hub", "requests"]
|
||||||
huggingface-hub = ["huggingface_hub"]
|
huggingface-hub = ["huggingface_hub"]
|
||||||
images = ["ftfy", "pillow", "regex", "torch", "torchvision"]
|
images = ["ftfy", "pillow", "regex", "torch", "torchvision"]
|
||||||
json = ["llama-hub"]
|
json = ["llama-hub"]
|
||||||
llama2 = ["replicate"]
|
llama2 = ["replicate"]
|
||||||
milvus = ["pymilvus"]
|
milvus = ["pymilvus"]
|
||||||
|
mysql = ["mysql-connector-python"]
|
||||||
opensearch = ["opensearch-py"]
|
opensearch = ["opensearch-py"]
|
||||||
opensource = ["gpt4all", "sentence-transformers", "torch"]
|
opensource = ["gpt4all", "sentence-transformers", "torch"]
|
||||||
pinecone = ["pinecone-client"]
|
pinecone = ["pinecone-client"]
|
||||||
@@ -7416,8 +7562,9 @@ streamlit = []
|
|||||||
vertexai = ["google-cloud-aiplatform"]
|
vertexai = ["google-cloud-aiplatform"]
|
||||||
weaviate = ["weaviate-client"]
|
weaviate = ["weaviate-client"]
|
||||||
whatsapp = ["flask", "twilio"]
|
whatsapp = ["flask", "twilio"]
|
||||||
|
youtube = ["youtube-transcript-api", "yt_dlp"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.12"
|
python-versions = ">=3.9,<3.12"
|
||||||
content-hash = "fa041b870ce060414e7c2d2bc21c2c4909aff117b40a161f2eaafafe44136597"
|
content-hash = "ea063cadfefd23d4c9b2a25c9096efe6bcedc367136d819ccb1fd2f510a91206"
|
||||||
|
|||||||
+14
-5
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "embedchain"
|
name = "embedchain"
|
||||||
version = "0.1.6"
|
version = "0.1.17"
|
||||||
description = "Data platform for LLMs - Load, index, retrieve and sync any unstructured data"
|
description = "Data platform for LLMs - Load, index, retrieve and sync any unstructured data"
|
||||||
authors = [
|
authors = [
|
||||||
"Taranjeet Singh <taranjeet@embedchain.ai>",
|
"Taranjeet Singh <taranjeet@embedchain.ai>",
|
||||||
@@ -90,10 +90,10 @@ color = true
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<3.12"
|
python = ">=3.9,<3.12"
|
||||||
python-dotenv = "^1.0.0"
|
python-dotenv = "^1.0.0"
|
||||||
langchain = "^0.0.332"
|
langchain = "^0.0.336"
|
||||||
requests = "^2.31.0"
|
requests = "^2.31.0"
|
||||||
openai = ">=1.1.1"
|
openai = ">=1.1.1"
|
||||||
chromadb = "^0.4.16"
|
chromadb = "^0.4.17"
|
||||||
posthog = "^3.0.2"
|
posthog = "^3.0.2"
|
||||||
tiktoken = { version = "^0.4.0", optional = true }
|
tiktoken = { version = "^0.4.0", optional = true }
|
||||||
youtube-transcript-api = { version = "^0.6.1", optional = true }
|
youtube-transcript-api = { version = "^0.6.1", optional = true }
|
||||||
@@ -120,7 +120,7 @@ weaviate-client = { version = "^3.24.1", optional = true }
|
|||||||
docx2txt = { version = "^0.8", optional = true }
|
docx2txt = { version = "^0.8", optional = true }
|
||||||
pinecone-client = { version = "^2.2.4", optional = true }
|
pinecone-client = { version = "^2.2.4", optional = true }
|
||||||
qdrant-client = { version = "1.6.3", optional = true }
|
qdrant-client = { version = "1.6.3", optional = true }
|
||||||
unstructured = {extras = ["local-inference"], version = "^0.10.18", optional = true}
|
unstructured = {extras = ["local-inference", "all-docs"], version = "^0.10.18", optional = true}
|
||||||
pillow = { version = "10.0.1", optional = true }
|
pillow = { version = "10.0.1", optional = true }
|
||||||
torchvision = { version = ">=0.15.1, !=0.15.2", optional = true }
|
torchvision = { version = ">=0.15.1, !=0.15.2", optional = true }
|
||||||
ftfy = { version = "6.1.1", optional = true }
|
ftfy = { version = "6.1.1", optional = true }
|
||||||
@@ -133,6 +133,9 @@ schema = "^0.7.5"
|
|||||||
psycopg = { version = "^3.1.12", optional = true }
|
psycopg = { version = "^3.1.12", optional = true }
|
||||||
psycopg-binary = { version = "^3.1.12", optional = true }
|
psycopg-binary = { version = "^3.1.12", optional = true }
|
||||||
psycopg-pool = { version = "^3.1.8", optional = true }
|
psycopg-pool = { version = "^3.1.8", optional = true }
|
||||||
|
mysql-connector-python = { version = "^8.1.0", optional = true }
|
||||||
|
gitpython = { version = "^3.1.38", optional = true }
|
||||||
|
yt_dlp = { version = "^2023.11.14", optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.3.0"
|
black = "^23.3.0"
|
||||||
@@ -166,7 +169,7 @@ huggingface_hub=["huggingface_hub"]
|
|||||||
cohere = ["cohere"]
|
cohere = ["cohere"]
|
||||||
milvus = ["pymilvus"]
|
milvus = ["pymilvus"]
|
||||||
dataloaders=[
|
dataloaders=[
|
||||||
"youtube-transcripts-api",
|
"youtube-transcript-api",
|
||||||
"beautifulsoup4",
|
"beautifulsoup4",
|
||||||
"docx2txt",
|
"docx2txt",
|
||||||
"duckduckgo-search",
|
"duckduckgo-search",
|
||||||
@@ -188,6 +191,12 @@ gmail = [
|
|||||||
]
|
]
|
||||||
json = ["llama-hub"]
|
json = ["llama-hub"]
|
||||||
postgres = ["psycopg", "psycopg-binary", "psycopg-pool"]
|
postgres = ["psycopg", "psycopg-binary", "psycopg-pool"]
|
||||||
|
mysql = ["mysql-connector-python"]
|
||||||
|
git = ["gitpython"]
|
||||||
|
youtube = [
|
||||||
|
"yt_dlp",
|
||||||
|
"youtube-transcript-api",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poetry.group.docs.dependencies]
|
[tool.poetry.group.docs.dependencies]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from embedchain.chunkers.common_chunker import CommonChunker
|
||||||
|
from embedchain.chunkers.discourse import DiscourseChunker
|
||||||
from embedchain.chunkers.docs_site import DocsSiteChunker
|
from embedchain.chunkers.docs_site import DocsSiteChunker
|
||||||
from embedchain.chunkers.docx_file import DocxFileChunker
|
from embedchain.chunkers.docx_file import DocxFileChunker
|
||||||
from embedchain.chunkers.gmail import GmailChunker
|
from embedchain.chunkers.gmail import GmailChunker
|
||||||
@@ -9,6 +11,7 @@ from embedchain.chunkers.pdf_file import PdfFileChunker
|
|||||||
from embedchain.chunkers.postgres import PostgresChunker
|
from embedchain.chunkers.postgres import PostgresChunker
|
||||||
from embedchain.chunkers.qna_pair import QnaPairChunker
|
from embedchain.chunkers.qna_pair import QnaPairChunker
|
||||||
from embedchain.chunkers.sitemap import SitemapChunker
|
from embedchain.chunkers.sitemap import SitemapChunker
|
||||||
|
from embedchain.chunkers.slack import SlackChunker
|
||||||
from embedchain.chunkers.table import TableChunker
|
from embedchain.chunkers.table import TableChunker
|
||||||
from embedchain.chunkers.text import TextChunker
|
from embedchain.chunkers.text import TextChunker
|
||||||
from embedchain.chunkers.web_page import WebPageChunker
|
from embedchain.chunkers.web_page import WebPageChunker
|
||||||
@@ -35,6 +38,9 @@ chunker_common_config = {
|
|||||||
OpenAPIChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
OpenAPIChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
||||||
GmailChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
GmailChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
||||||
PostgresChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
PostgresChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
||||||
|
SlackChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
||||||
|
DiscourseChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
||||||
|
CommonChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def test_whole_app(app_instance, mocker):
|
|||||||
knowledge = "lorem ipsum dolor sit amet, consectetur adipiscing"
|
knowledge = "lorem ipsum dolor sit amet, consectetur adipiscing"
|
||||||
|
|
||||||
mocker.patch.object(EmbedChain, "add")
|
mocker.patch.object(EmbedChain, "add")
|
||||||
mocker.patch.object(EmbedChain, "retrieve_from_database")
|
mocker.patch.object(EmbedChain, "_retrieve_from_database")
|
||||||
mocker.patch.object(BaseLlm, "get_answer_from_llm", return_value=knowledge)
|
mocker.patch.object(BaseLlm, "get_answer_from_llm", return_value=knowledge)
|
||||||
mocker.patch.object(BaseLlm, "get_llm_model_answer", return_value=knowledge)
|
mocker.patch.object(BaseLlm, "get_llm_model_answer", return_value=knowledge)
|
||||||
mocker.patch.object(BaseLlm, "generate_prompt")
|
mocker.patch.object(BaseLlm, "generate_prompt")
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class TestJsonSerializable(unittest.TestCase):
|
|||||||
app: SecondTestClass = SecondTestClass().deserialize(serial)
|
app: SecondTestClass = SecondTestClass().deserialize(serial)
|
||||||
self.assertTrue(app.default)
|
self.assertTrue(app.default)
|
||||||
# If we register and try again with the same serial, it should work
|
# If we register and try again with the same serial, it should work
|
||||||
SecondTestClass.register_class_as_deserializable(SecondTestClass)
|
SecondTestClass._register_class_as_deserializable(SecondTestClass)
|
||||||
app: SecondTestClass = SecondTestClass().deserialize(serial)
|
app: SecondTestClass = SecondTestClass().deserialize(serial)
|
||||||
self.assertFalse(app.default)
|
self.assertFalse(app.default)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class TestApp(unittest.TestCase):
|
|||||||
os.environ["OPENAI_API_KEY"] = "test_key"
|
os.environ["OPENAI_API_KEY"] = "test_key"
|
||||||
self.app = App(config=AppConfig(collect_metrics=False))
|
self.app = App(config=AppConfig(collect_metrics=False))
|
||||||
|
|
||||||
@patch.object(App, "retrieve_from_database", return_value=["Test context"])
|
@patch.object(App, "_retrieve_from_database", return_value=["Test context"])
|
||||||
@patch.object(BaseLlm, "get_answer_from_llm", return_value="Test answer")
|
@patch.object(BaseLlm, "get_answer_from_llm", return_value="Test answer")
|
||||||
def test_chat_with_memory(self, mock_get_answer, mock_retrieve):
|
def test_chat_with_memory(self, mock_get_answer, mock_retrieve):
|
||||||
"""
|
"""
|
||||||
@@ -28,7 +28,7 @@ class TestApp(unittest.TestCase):
|
|||||||
- After the first call, 'memory.chat_memory.add_user_message' and 'memory.chat_memory.add_ai_message' are
|
- After the first call, 'memory.chat_memory.add_user_message' and 'memory.chat_memory.add_ai_message' are
|
||||||
- During the second call, the 'chat' method uses the chat history from the first call.
|
- During the second call, the 'chat' method uses the chat history from the first call.
|
||||||
|
|
||||||
The test isolates the 'chat' method behavior by mocking out 'retrieve_from_database', 'get_answer_from_llm' and
|
The test isolates the 'chat' method behavior by mocking out '_retrieve_from_database', 'get_answer_from_llm' and
|
||||||
'memory' methods.
|
'memory' methods.
|
||||||
"""
|
"""
|
||||||
config = AppConfig(collect_metrics=False)
|
config = AppConfig(collect_metrics=False)
|
||||||
@@ -42,7 +42,7 @@ class TestApp(unittest.TestCase):
|
|||||||
self.assertEqual(second_answer, "Test answer")
|
self.assertEqual(second_answer, "Test answer")
|
||||||
mock_history.assert_called_with(app.config.id, "Test query 2", "Test answer")
|
mock_history.assert_called_with(app.config.id, "Test query 2", "Test answer")
|
||||||
|
|
||||||
@patch.object(App, "retrieve_from_database", return_value=["Test context"])
|
@patch.object(App, "_retrieve_from_database", return_value=["Test context"])
|
||||||
@patch.object(BaseLlm, "get_answer_from_llm", return_value="Test answer")
|
@patch.object(BaseLlm, "get_answer_from_llm", return_value="Test answer")
|
||||||
def test_template_replacement(self, mock_get_answer, mock_retrieve):
|
def test_template_replacement(self, mock_get_answer, mock_retrieve):
|
||||||
"""
|
"""
|
||||||
@@ -73,7 +73,7 @@ class TestApp(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
Test where filter
|
Test where filter
|
||||||
"""
|
"""
|
||||||
with patch.object(self.app, "retrieve_from_database") as mock_retrieve:
|
with patch.object(self.app, "_retrieve_from_database") as mock_retrieve:
|
||||||
mock_retrieve.return_value = ["Test context"]
|
mock_retrieve.return_value = ["Test context"]
|
||||||
with patch.object(self.app.llm, "get_llm_model_answer") as mock_answer:
|
with patch.object(self.app.llm, "get_llm_model_answer") as mock_answer:
|
||||||
mock_answer.return_value = "Test answer"
|
mock_answer.return_value = "Test answer"
|
||||||
@@ -89,19 +89,19 @@ class TestApp(unittest.TestCase):
|
|||||||
def test_chat_with_where_in_chat_config(self):
|
def test_chat_with_where_in_chat_config(self):
|
||||||
"""
|
"""
|
||||||
This test checks the functionality of the 'chat' method in the App class.
|
This test checks the functionality of the 'chat' method in the App class.
|
||||||
It simulates a scenario where the 'retrieve_from_database' method returns a context list based on
|
It simulates a scenario where the '_retrieve_from_database' method returns a context list based on
|
||||||
a where filter and 'get_llm_model_answer' returns an expected answer string.
|
a where filter and 'get_llm_model_answer' returns an expected answer string.
|
||||||
|
|
||||||
The 'chat' method is expected to call 'retrieve_from_database' with the where filter specified
|
The 'chat' method is expected to call '_retrieve_from_database' with the where filter specified
|
||||||
in the BaseLlmConfig and 'get_llm_model_answer' methods appropriately and return the right answer.
|
in the BaseLlmConfig and 'get_llm_model_answer' methods appropriately and return the right answer.
|
||||||
|
|
||||||
Key assumptions tested:
|
Key assumptions tested:
|
||||||
- 'retrieve_from_database' method is called exactly once with arguments: "Test query" and an instance of
|
- '_retrieve_from_database' method is called exactly once with arguments: "Test query" and an instance of
|
||||||
BaseLlmConfig.
|
BaseLlmConfig.
|
||||||
- 'get_llm_model_answer' is called exactly once. The specific arguments are not checked in this test.
|
- 'get_llm_model_answer' is called exactly once. The specific arguments are not checked in this test.
|
||||||
- 'chat' method returns the value it received from 'get_llm_model_answer'.
|
- 'chat' method returns the value it received from 'get_llm_model_answer'.
|
||||||
|
|
||||||
The test isolates the 'chat' method behavior by mocking out 'retrieve_from_database' and
|
The test isolates the 'chat' method behavior by mocking out '_retrieve_from_database' and
|
||||||
'get_llm_model_answer' methods.
|
'get_llm_model_answer' methods.
|
||||||
"""
|
"""
|
||||||
with patch.object(self.app.llm, "get_llm_model_answer") as mock_answer:
|
with patch.object(self.app.llm, "get_llm_model_answer") as mock_answer:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def app():
|
|||||||
|
|
||||||
@patch("chromadb.api.models.Collection.Collection.add", MagicMock)
|
@patch("chromadb.api.models.Collection.Collection.add", MagicMock)
|
||||||
def test_query(app):
|
def test_query(app):
|
||||||
with patch.object(app, "retrieve_from_database") as mock_retrieve:
|
with patch.object(app, "_retrieve_from_database") as mock_retrieve:
|
||||||
mock_retrieve.return_value = ["Test context"]
|
mock_retrieve.return_value = ["Test context"]
|
||||||
with patch.object(app.llm, "get_llm_model_answer") as mock_answer:
|
with patch.object(app.llm, "get_llm_model_answer") as mock_answer:
|
||||||
mock_answer.return_value = "Test answer"
|
mock_answer.return_value = "Test answer"
|
||||||
@@ -58,7 +58,7 @@ def test_app_passing(mock_get_answer):
|
|||||||
|
|
||||||
@patch("chromadb.api.models.Collection.Collection.add", MagicMock)
|
@patch("chromadb.api.models.Collection.Collection.add", MagicMock)
|
||||||
def test_query_with_where_in_params(app):
|
def test_query_with_where_in_params(app):
|
||||||
with patch.object(app, "retrieve_from_database") as mock_retrieve:
|
with patch.object(app, "_retrieve_from_database") as mock_retrieve:
|
||||||
mock_retrieve.return_value = ["Test context"]
|
mock_retrieve.return_value = ["Test context"]
|
||||||
with patch.object(app.llm, "get_llm_model_answer") as mock_answer:
|
with patch.object(app.llm, "get_llm_model_answer") as mock_answer:
|
||||||
mock_answer.return_value = "Test answer"
|
mock_answer.return_value = "Test answer"
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from embedchain.loaders.discourse import DiscourseLoader
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def discourse_loader_config():
|
||||||
|
return {
|
||||||
|
"domain": "https://example.com/",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def discourse_loader(discourse_loader_config):
|
||||||
|
return DiscourseLoader(config=discourse_loader_config)
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_init_with_valid_config():
|
||||||
|
config = {"domain": "https://example.com/"}
|
||||||
|
loader = DiscourseLoader(config=config)
|
||||||
|
assert loader.domain == "https://example.com/"
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_init_with_missing_config():
|
||||||
|
with pytest.raises(ValueError, match="DiscourseLoader requires a config"):
|
||||||
|
DiscourseLoader()
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_init_with_missing_domain():
|
||||||
|
config = {"another_key": "value"}
|
||||||
|
with pytest.raises(ValueError, match="DiscourseLoader requires a domain"):
|
||||||
|
DiscourseLoader(config=config)
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_check_query_with_valid_query(discourse_loader):
|
||||||
|
discourse_loader._check_query("sample query")
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_check_query_with_empty_query(discourse_loader):
|
||||||
|
with pytest.raises(ValueError, match="DiscourseLoader requires a query"):
|
||||||
|
discourse_loader._check_query("")
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_check_query_with_invalid_query_type(discourse_loader):
|
||||||
|
with pytest.raises(ValueError, match="DiscourseLoader requires a query"):
|
||||||
|
discourse_loader._check_query(123)
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_load_post_with_valid_post_id(discourse_loader, monkeypatch):
|
||||||
|
def mock_get(*args, **kwargs):
|
||||||
|
class MockResponse:
|
||||||
|
def json(self):
|
||||||
|
return {"raw": "Sample post content"}
|
||||||
|
|
||||||
|
def raise_for_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return MockResponse()
|
||||||
|
|
||||||
|
monkeypatch.setattr(requests, "get", mock_get)
|
||||||
|
|
||||||
|
post_data = discourse_loader._load_post(123)
|
||||||
|
|
||||||
|
assert post_data["content"] == "Sample post content"
|
||||||
|
assert "meta_data" in post_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_load_post_with_invalid_post_id(discourse_loader, monkeypatch, caplog):
|
||||||
|
def mock_get(*args, **kwargs):
|
||||||
|
class MockResponse:
|
||||||
|
def raise_for_status(self):
|
||||||
|
raise requests.exceptions.RequestException("Test error")
|
||||||
|
|
||||||
|
return MockResponse()
|
||||||
|
|
||||||
|
monkeypatch.setattr(requests, "get", mock_get)
|
||||||
|
|
||||||
|
discourse_loader._load_post(123)
|
||||||
|
|
||||||
|
assert "Failed to load post" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_discourse_loader_load_data_with_valid_query(discourse_loader, monkeypatch):
|
||||||
|
def mock_get(*args, **kwargs):
|
||||||
|
class MockResponse:
|
||||||
|
def json(self):
|
||||||
|
return {"grouped_search_result": {"post_ids": [123, 456, 789]}}
|
||||||
|
|
||||||
|
def raise_for_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return MockResponse()
|
||||||
|
|
||||||
|
monkeypatch.setattr(requests, "get", mock_get)
|
||||||
|
|
||||||
|
def mock_load_post(*args, **kwargs):
|
||||||
|
return {
|
||||||
|
"content": "Sample post content",
|
||||||
|
"meta_data": {
|
||||||
|
"url": "https://example.com/posts/123.json",
|
||||||
|
"created_at": "2021-01-01",
|
||||||
|
"username": "test_user",
|
||||||
|
"topic_slug": "test_topic",
|
||||||
|
"score": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(discourse_loader, "_load_post", mock_load_post)
|
||||||
|
|
||||||
|
data = discourse_loader.load_data("sample query")
|
||||||
|
|
||||||
|
assert len(data["data"]) == 3
|
||||||
|
assert data["data"][0]["content"] == "Sample post content"
|
||||||
|
assert data["data"][0]["meta_data"]["url"] == "https://example.com/posts/123.json"
|
||||||
|
assert data["data"][0]["meta_data"]["created_at"] == "2021-01-01"
|
||||||
|
assert data["data"][0]["meta_data"]["username"] == "test_user"
|
||||||
|
assert data["data"][0]["meta_data"]["topic_slug"] == "test_topic"
|
||||||
|
assert data["data"][0]["meta_data"]["score"] == 10
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import hashlib
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from embedchain.loaders.mysql import MySQLLoader
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mysql_loader(mocker):
|
||||||
|
with mocker.patch("mysql.connector.connection.MySQLConnection"):
|
||||||
|
config = {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": "3306",
|
||||||
|
"user": "your_username",
|
||||||
|
"password": "your_password",
|
||||||
|
"database": "your_database",
|
||||||
|
}
|
||||||
|
loader = MySQLLoader(config=config)
|
||||||
|
yield loader
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_initialization(mysql_loader):
|
||||||
|
assert mysql_loader.config is not None
|
||||||
|
assert mysql_loader.connection is not None
|
||||||
|
assert mysql_loader.cursor is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_invalid_config():
|
||||||
|
with pytest.raises(ValueError, match="Invalid sql config: None"):
|
||||||
|
MySQLLoader(config=None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_setup_loader_successful(mysql_loader):
|
||||||
|
assert mysql_loader.connection is not None
|
||||||
|
assert mysql_loader.cursor is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_setup_loader_connection_error(mysql_loader, mocker):
|
||||||
|
mocker.patch("mysql.connector.connection.MySQLConnection", side_effect=IOError("Mocked connection error"))
|
||||||
|
with pytest.raises(ValueError, match="Unable to connect with the given config:"):
|
||||||
|
mysql_loader._setup_loader(config={})
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_check_query_successful(mysql_loader):
|
||||||
|
query = "SELECT * FROM table"
|
||||||
|
mysql_loader._check_query(query=query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_check_query_invalid(mysql_loader):
|
||||||
|
with pytest.raises(ValueError, match="Invalid mysql query: 123"):
|
||||||
|
mysql_loader._check_query(query=123)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_load_data_successful(mysql_loader, mocker):
|
||||||
|
mock_cursor = MagicMock()
|
||||||
|
mocker.patch.object(mysql_loader, "cursor", mock_cursor)
|
||||||
|
mock_cursor.fetchall.return_value = [(1, "data1"), (2, "data2")]
|
||||||
|
|
||||||
|
query = "SELECT * FROM table"
|
||||||
|
result = mysql_loader.load_data(query)
|
||||||
|
|
||||||
|
assert "doc_id" in result
|
||||||
|
assert "data" in result
|
||||||
|
assert len(result["data"]) == 2
|
||||||
|
assert result["data"][0]["meta_data"]["url"] == query
|
||||||
|
assert result["data"][1]["meta_data"]["url"] == query
|
||||||
|
|
||||||
|
doc_id = hashlib.sha256((query + ", ".join([d["content"] for d in result["data"]])).encode()).hexdigest()
|
||||||
|
|
||||||
|
assert result["doc_id"] == doc_id
|
||||||
|
assert mock_cursor.execute.called_with(query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mysql_loader_load_data_invalid_query(mysql_loader):
|
||||||
|
with pytest.raises(ValueError, match="Invalid mysql query: 123"):
|
||||||
|
mysql_loader.load_data(query=123)
|
||||||
@@ -36,8 +36,8 @@ def test_load_data(postgres_loader, monkeypatch):
|
|||||||
assert "doc_id" in result
|
assert "doc_id" in result
|
||||||
assert "data" in result
|
assert "data" in result
|
||||||
assert len(result["data"]) == 2
|
assert len(result["data"]) == 2
|
||||||
assert result["data"][0]["meta_data"]["url"] == f"postgres_query-({query})"
|
assert result["data"][0]["meta_data"]["url"] == query
|
||||||
assert result["data"][1]["meta_data"]["url"] == f"postgres_query-({query})"
|
assert result["data"][1]["meta_data"]["url"] == query
|
||||||
assert mock_cursor.execute.called_with(query)
|
assert mock_cursor.execute.called_with(query)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from embedchain.loaders.slack import SlackLoader
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def slack_loader(mocker, monkeypatch):
|
||||||
|
# Mocking necessary dependencies
|
||||||
|
mocker.patch("slack_sdk.WebClient")
|
||||||
|
mocker.patch("ssl.create_default_context")
|
||||||
|
mocker.patch("certifi.where")
|
||||||
|
|
||||||
|
monkeypatch.setenv("SLACK_USER_TOKEN", "slack_user_token")
|
||||||
|
|
||||||
|
return SlackLoader()
|
||||||
|
|
||||||
|
|
||||||
|
def test_slack_loader_initialization(slack_loader):
|
||||||
|
assert slack_loader.client is not None
|
||||||
|
assert slack_loader.config == {"base_url": "https://www.slack.com/api/"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_slack_loader_setup_loader(slack_loader):
|
||||||
|
slack_loader._setup_loader({"base_url": "https://custom.slack.api/"})
|
||||||
|
|
||||||
|
assert slack_loader.client is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_slack_loader_check_query(slack_loader):
|
||||||
|
valid_json_query = "test_query"
|
||||||
|
invalid_query = 123
|
||||||
|
|
||||||
|
slack_loader._check_query(valid_json_query)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
slack_loader._check_query(invalid_query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_slack_loader_load_data(slack_loader, mocker):
|
||||||
|
valid_json_query = "in:random"
|
||||||
|
|
||||||
|
mocker.patch.object(slack_loader.client, "search_messages", return_value={"messages": {}})
|
||||||
|
|
||||||
|
result = slack_loader.load_data(valid_json_query)
|
||||||
|
|
||||||
|
assert "doc_id" in result
|
||||||
|
assert "data" in result
|
||||||
@@ -27,7 +27,7 @@ def test_load_data(web_page_loader):
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
with patch("embedchain.loaders.web_page.requests.get", return_value=mock_response):
|
with patch("embedchain.loaders.web_page.WebPageLoader._session.get", return_value=mock_response):
|
||||||
result = web_page_loader.load_data(page_url)
|
result = web_page_loader.load_data(page_url)
|
||||||
|
|
||||||
content = web_page_loader._get_clean_content(mock_response.content, page_url)
|
content = web_page_loader._get_clean_content(mock_response.content, page_url)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class TestAnonymousTelemetry:
|
|||||||
mocker.patch("embedchain.telemetry.posthog.CONFIG_FILE", str(config_file))
|
mocker.patch("embedchain.telemetry.posthog.CONFIG_FILE", str(config_file))
|
||||||
telemetry = AnonymousTelemetry()
|
telemetry = AnonymousTelemetry()
|
||||||
|
|
||||||
user_id = telemetry.get_user_id()
|
user_id = telemetry._get_user_id()
|
||||||
assert user_id == "unique_user_id"
|
assert user_id == "unique_user_id"
|
||||||
assert config_file.read() == '{"user_id": "unique_user_id"}'
|
assert config_file.read() == '{"user_id": "unique_user_id"}'
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -9,6 +9,7 @@ CONFIG_YAMLS = [
|
|||||||
"configs/chunker.yaml",
|
"configs/chunker.yaml",
|
||||||
"configs/cohere.yaml",
|
"configs/cohere.yaml",
|
||||||
"configs/full-stack.yaml",
|
"configs/full-stack.yaml",
|
||||||
|
"configs/gpt4.yaml",
|
||||||
"configs/gpt4all.yaml",
|
"configs/gpt4all.yaml",
|
||||||
"configs/huggingface.yaml",
|
"configs/huggingface.yaml",
|
||||||
"configs/jina.yaml",
|
"configs/jina.yaml",
|
||||||
@@ -21,16 +22,15 @@ CONFIG_YAMLS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TestAllConfigYamls:
|
def test_all_config_yamls():
|
||||||
def test_all_config_yamls(self):
|
"""Test that all config yamls are valid."""
|
||||||
"""Test that all config yamls are valid."""
|
for config_yaml in CONFIG_YAMLS:
|
||||||
for config_yaml in CONFIG_YAMLS:
|
with open(config_yaml, "r") as f:
|
||||||
with open(config_yaml, "r") as f:
|
config = yaml.safe_load(f)
|
||||||
config = yaml.safe_load(f)
|
assert config is not None
|
||||||
assert config is not None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validate_yaml_config(config)
|
validate_yaml_config(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in {config_yaml}: {e}")
|
print(f"Error in {config_yaml}: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
Reference in New Issue
Block a user