Add rest-api example (#889)

This commit is contained in:
Sidharth Mohanty
2023-11-03 13:02:51 +05:30
committed by GitHub
parent a054f7be9c
commit 8dd5cb9602
44 changed files with 1217 additions and 118 deletions

1
.gitignore vendored
View File

@@ -76,7 +76,6 @@ docs/_build/
target/
# Jupyter Notebook
*.yaml
# IPython
profile_default/

View File

@@ -26,6 +26,14 @@ Book a [1-on-1 Session](https://cal.com/taranjeetio/ec) with Taranjeet, the foun
pip install --upgrade embedchain
```
To run Embedchain as a REST API server use,
```bash
docker run -d --name embedchain -p 8000:8000 embedchain/app:rest-api-latest
```
Navigate to http://0.0.0.0:8000/docs to interact with the API.
## 🔍 Demo
Try out embedchain in your browser:

View File

@@ -1,7 +1,7 @@
llm:
provider: anthropic
model: 'claude-instant-1'
config:
model: 'claude-instant-1'
temperature: 0.5
max_tokens: 1000
top_p: 1

View File

@@ -4,8 +4,8 @@ app:
llm:
provider: azure_openai
model: gpt-35-turbo
config:
model: gpt-35-turbo
deployment_name: your_llm_deployment_name
temperature: 0.5
max_tokens: 1000

View File

@@ -5,8 +5,8 @@ app:
llm:
provider: openai
model: 'gpt-3.5-turbo'
config:
model: 'gpt-3.5-turbo'
temperature: 0.5
max_tokens: 1000
top_p: 1

View File

@@ -1,7 +1,7 @@
llm:
provider: cohere
model: large
config:
model: large
temperature: 0.5
max_tokens: 1000
top_p: 1

View File

@@ -4,8 +4,8 @@ app:
llm:
provider: openai
model: 'gpt-3.5-turbo'
config:
model: 'gpt-3.5-turbo'
temperature: 0.5
max_tokens: 1000
top_p: 1

View File

@@ -1,7 +1,7 @@
llm:
provider: huggingface
model: 'google/flan-t5-xxl'
config:
model: 'google/flan-t5-xxl'
temperature: 0.5
max_tokens: 1000
top_p: 0.5

View File

@@ -1,7 +1,7 @@
llm:
provider: llama2
model: 'a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5'
config:
model: 'a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5'
temperature: 0.5
max_tokens: 1000
top_p: 0.5

View File

@@ -7,8 +7,8 @@ app:
llm:
provider: openai
model: 'gpt-3.5-turbo'
config:
model: 'gpt-3.5-turbo'
temperature: 0.5
max_tokens: 1000
top_p: 1

View File

@@ -1,6 +1,6 @@
llm:
provider: vertexai
model: 'chat-bison'
config:
model: 'chat-bison'
temperature: 0.5
top_p: 0.5

View File

@@ -0,0 +1,3 @@
---
openapi: post /{app_id}/add
---

View File

@@ -0,0 +1,3 @@
---
openapi: post /{app_id}/chat
---

View File

@@ -0,0 +1,3 @@
---
openapi: get /ping
---

View File

@@ -0,0 +1,3 @@
---
openapi: post /create
---

View File

@@ -0,0 +1,3 @@
---
openapi: delete /{app_id}/delete
---

View File

@@ -0,0 +1,3 @@
---
openapi: post /{app_id}/deploy
---

View File

@@ -0,0 +1,3 @@
---
openapi: get /apps
---

View File

@@ -0,0 +1,3 @@
---
openapi: get /{app_id}/data
---

View File

@@ -0,0 +1,102 @@
---
title: "🌍 Getting Started"
---
## Quick Start
To run Embedchain as a REST API server use,
```bash
docker run -d --name embedchain -p 8000:8000 embedchain/app:rest-api-latest
```
Open up your browser and navigate to http://0.0.0.0:8000/docs to interact with the API. There is a full-fledged Swagger docs playground with all the information
about the API endpoints.
![Swagger Docs Screenshot](https://github.com/embedchain/embedchain/assets/73601258/299d81e5-a0df-407c-afc2-6fa2c4286844)
## Creating your first App
App requires an `app_id` to be created. The `app_id` is a unique identifier for your app.
By default we will use the opensource **gpt4all** model to perform operations. You can also specify your own config by uploading a config YAML file.
For example, create a `config.yaml` file (adjust according to your requirements):
```yaml
app:
config:
id: "default-app"
llm:
provider: openai
config:
model: "gpt-3.5-turbo"
temperature: 0.5
max_tokens: 1000
top_p: 1
stream: false
template: |
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
$context
Query: $query
Helpful Answer:
vectordb:
provider: chroma
config:
collection_name: "rest-api-app"
dir: db
allow_reset: true
embedder:
provider: openai
config:
model: "text-embedding-ada-002"
```
To learn more about custom configurations, check out the [Custom configurations](https://docs.embedchain.ai/advanced/configuration).
To explore more examples of config YAMLs for Embedchain, visit [embedchain/configs](https://github.com/embedchain/embedchain/tree/main/configs).
Now, you can upload this config file in the request body.
**Note:** To use custom models, an **API key** might be required. Refer to the table below to determine the necessary API key for your provider.
| Keys | Providers |
| -------------------------- | ------------------------------ |
| `OPENAI_API_KEY ` | OpenAI, Azure OpenAI, Jina etc |
| `OPENAI_API_TYPE` | Azure OpenAI |
| `OPENAI_API_BASE` | Azure OpenAI |
| `OPENAI_API_VERSION` | Azure OpenAI |
| `COHERE_API_KEY` | Cohere |
| `ANTHROPIC_API_KEY` | Anthropic |
| `JINACHAT_API_KEY` | Jina |
| `HUGGINGFACE_ACCESS_TOKEN` | Huggingface |
| `REPLICATE_API_TOKEN` | LLAMA2 |
To provide them, you can simply run the docker command with the `-e` flag.
For example,
```bash
docker run -d --name embedchain -p 8000:8000 -e OPENAI_API_KEY=YOUR_API_KEY embedchain/app:rest-api-latest
```
Cool! This will create a new Embedchain App with the given `app_id`.
## Deploying your App to Embedchain Platform
This feature is very powerful as it allows the creation of a public API endpoint for your app, enabling queries from anywhere. This creates a _pipeline_
for your app that can sync the data time to time and provide you with the best results.
![My first Pipeline](https://github.com/embedchain/embedchain/assets/73601258/266a66e0-330e-4bb9-aa97-687d826cd3fa)
To utilize this functionality, visit app.embedchain.ai and create an account. Subsequently, generate a new [API KEY](https://app.embedchain.ai/settings/keys/).
![Create Embedchain API Key](https://github.com/embedchain/embedchain/assets/73601258/791e92cc-4a6f-4740-94c2-f11cce13e93b)
Using this API key, you can deploy your app to the platform.

View File

@@ -0,0 +1,3 @@
---
openapi: post /{app_id}/query
---

View File

@@ -1,93 +0,0 @@
---
title: '🌍 API Server'
---
The API server example can be found [here](https://github.com/embedchain/embedchain/tree/main/examples/api_server).
It is a Flask based server that integrates the `embedchain` package, offering endpoints to add, query, and chat to engage in conversations with a chatbot using JSON requests.
### 🐳 Docker Setup
- Open variables.env, and edit it to add your 🔑 `OPENAI_API_KEY`.
- To setup your api server using docker, run the following command inside this folder using your terminal.
```bash
docker-compose up --build
```
📝 Note: The build command might take a while to install all the packages depending on your system resources.
### 🚀 Usage Instructions
- Your api server is running on [http://localhost:5000/](http://localhost:5000/)
- To use the api server, make an api call to the endpoints `/add`, `/query` and `/chat` using the json formats discussed below.
- To add data sources to the bot (/add):
```json
// Request
{
"data_type": "your_data_type_here",
"url_or_text": "your_url_or_text_here"
}
// Response
{
"data": "Added data_type: url_or_text"
}
```
- To ask queries from the bot (/query):
```json
// Request
{
"question": "your_question_here"
}
// Response
{
"data": "your_answer_here"
}
```
- To chat with the bot (/chat):
```json
// Request
{
"question": "your_question_here"
}
// Response
{
"data": "your_answer_here"
}
```
### 📡 Curl Call Formats
- To add data sources to the bot (/add):
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"data_type": "your_data_type_here",
"url_or_text": "your_url_or_text_here"
}' \
http://localhost:5000/add
```
- To ask queries from the bot (/query):
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"question": "your_question_here"
}' \
http://localhost:5000/query
```
- To chat with the bot (/chat):
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"question": "your_question_here"
}' \
http://localhost:5000/chat
```
🎉 Happy Chatting! 🎉

View File

@@ -14,6 +14,7 @@
"dark": "#020415"
}
},
"openapi": ["/rest-api.json"],
"metadata": {
"og:image": "/images/og.png",
"twitter:site": "@embedchain"
@@ -28,8 +29,8 @@
"url": "https://twitter.com/embedchain"
},
{
"name":"Slack",
"url":"https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw"
"name": "Slack",
"url": "https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw"
},
{
"name": "Discord",
@@ -46,11 +47,20 @@
"navigation": [
{
"group": "Get started",
"pages": ["get-started/quickstart", "get-started/introduction", "get-started/faq", "get-started/examples"]
"pages": [
"get-started/quickstart",
"get-started/introduction",
"get-started/faq",
"get-started/examples"
]
},
{
"group": "Components",
"pages": ["components/llms", "components/embedding-models", "components/vector-databases"]
"pages": [
"components/llms",
"components/embedding-models",
"components/vector-databases"
]
},
{
"group": "Data sources",
@@ -81,16 +91,34 @@
"group": "Advanced",
"pages": ["advanced/configuration"]
},
{
"group": "Rest API",
"pages": [
"api-reference/getting-started",
"api-reference/check-status",
"api-reference/get-all-apps",
"api-reference/create-app",
"api-reference/query-an-app",
"api-reference/add-datasource-to-an-app",
"api-reference/get-datasources-associated-with-app-id",
"api-reference/deploy-app",
"api-reference/delete-app"
]
},
{
"group": "Examples",
"pages": ["examples/full_stack", "examples/api_server", "examples/discord_bot", "examples/slack_bot", "examples/telegram_bot", "examples/whatsapp_bot", "examples/poe_bot"]
"pages": [
"examples/full_stack",
"examples/discord_bot",
"examples/slack_bot",
"examples/telegram_bot",
"examples/whatsapp_bot",
"examples/poe_bot"
]
},
{
"group": "Community",
"pages": [
"community/connect-with-us",
"community/showcase"
]
"pages": ["community/connect-with-us", "community/showcase"]
},
{
"group": "Integrations",
@@ -108,16 +136,13 @@
},
{
"group": "Product",
"pages": [
"product/release-notes"
]
"pages": ["product/release-notes"]
}
],
"footerSocials": {
"website": "https://embedchain.ai",
"github": "https://github.com/embedchain/embedchain",
"slack":"https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw",
"slack": "https://join.slack.com/t/embedchain/shared_invite/zt-22uwz3c46-Zg7cIh5rOBteT_xe1jwLDw",
"discord": "https://discord.gg/6PzXDgEjG5",
"twitter": "https://twitter.com/embedchain",
"linkedin": "https://www.linkedin.com/company/embedchain"

428
docs/rest-api.json Normal file
View File

@@ -0,0 +1,428 @@
{
"openapi": "3.1.0",
"info": {
"title": "Embedchain REST API",
"description": "This is the REST API for Embedchain.",
"license": {
"name": "Apache 2.0",
"url": "https://github.com/embedchain/embedchain/blob/main/LICENSE"
},
"version": "0.0.1"
},
"paths": {
"/ping": {
"get": {
"tags": ["Utility"],
"summary": "Check Status",
"description": "Endpoint to check the status of the API.",
"operationId": "check_status_ping_get",
"responses": {
"200": {
"description": "Successful Response",
"content": { "application/json": { "schema": {} } }
}
}
}
},
"/apps": {
"get": {
"tags": ["Apps"],
"summary": "Get All Apps",
"description": "Get all apps.",
"operationId": "get_all_apps_apps_get",
"responses": {
"200": {
"description": "Successful Response",
"content": { "application/json": { "schema": {} } }
}
}
}
},
"/create": {
"post": {
"tags": ["Apps"],
"summary": "Create App",
"description": "Create a new app using App ID.",
"operationId": "create_app_using_default_config_create_post",
"parameters": [
{
"name": "app_id",
"in": "query",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Body_create_app_using_default_config_create_post"
}
],
"title": "Body"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DefaultResponse" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/{app_id}/data": {
"get": {
"tags": ["Apps"],
"summary": "Get Datasources Associated With App Id",
"description": "Get all datasources for an app.",
"operationId": "get_datasources_associated_with_app_id__app_id__data_get",
"parameters": [
{
"name": "app_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": { "application/json": { "schema": {} } }
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/{app_id}/add": {
"post": {
"tags": ["Apps"],
"summary": "Add Datasource To An App",
"description": "Add a source to an existing app.",
"operationId": "add_datasource_to_an_app__app_id__add_post",
"parameters": [
{
"name": "app_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/SourceApp" }
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DefaultResponse" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/{app_id}/query": {
"post": {
"tags": ["Apps"],
"summary": "Query An App",
"description": "Query an existing app.",
"operationId": "query_an_app__app_id__query_post",
"parameters": [
{
"name": "app_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/QueryApp" }
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DefaultResponse" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/{app_id}/chat": {
"post": {
"tags": ["Apps"],
"summary": "Chat With An App",
"description": "Query an existing app.\n\napp_id: The ID of the app. Use \"default\" for the default app.\n\nmessage: The message that you want to send to the App.",
"operationId": "chat_with_an_app__app_id__chat_post",
"parameters": [
{
"name": "app_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/MessageApp" }
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DefaultResponse" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/{app_id}/deploy": {
"post": {
"tags": ["Apps"],
"summary": "Deploy App",
"description": "Deploy an existing app.",
"operationId": "deploy_app__app_id__deploy_post",
"parameters": [
{
"name": "app_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DeployAppRequest" }
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DefaultResponse" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/{app_id}/delete": {
"delete": {
"tags": ["Apps"],
"summary": "Delete App",
"description": "Delete an existing app.",
"operationId": "delete_app__app_id__delete_delete",
"parameters": [
{
"name": "app_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "App Id" }
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DefaultResponse" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"Body_create_app_using_default_config_create_post": {
"properties": {
"config": { "type": "string", "format": "binary", "title": "Config" }
},
"type": "object",
"title": "Body_create_app_using_default_config_create_post"
},
"DefaultResponse": {
"properties": { "response": { "type": "string", "title": "Response" } },
"type": "object",
"required": ["response"],
"title": "DefaultResponse"
},
"DeployAppRequest": {
"properties": {
"api_key": {
"type": "string",
"title": "Api Key",
"description": "The Embedchain API key for App deployments.",
"default": ""
}
},
"type": "object",
"title": "DeployAppRequest",
"example":{
"api_key":"ec-xxx"
}
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": { "$ref": "#/components/schemas/ValidationError" },
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"MessageApp": {
"properties": {
"message": {
"type": "string",
"title": "Message",
"description": "The message that you want to send to the App.",
"default": ""
}
},
"type": "object",
"title": "MessageApp"
},
"QueryApp": {
"properties": {
"query": {
"type": "string",
"title": "Query",
"description": "The query that you want to ask the App.",
"default": ""
}
},
"type": "object",
"title": "QueryApp",
"example":{
"query":"Who is Elon Musk?"
}
},
"SourceApp": {
"properties": {
"source": {
"type": "string",
"title": "Source",
"description": "The source that you want to add to the App.",
"default": ""
},
"data_type": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Data Type",
"description": "The type of data to add, remove it if you want Embedchain to detect it automatically.",
"default": ""
}
},
"type": "object",
"title": "SourceApp",
"example":{
"source":"https://en.wikipedia.org/wiki/Elon_Musk"
}
},
"ValidationError": {
"properties": {
"loc": {
"items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] },
"type": "array",
"title": "Location"
},
"msg": { "type": "string", "title": "Message" },
"type": { "type": "string", "title": "Error Type" }
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError"
}
}
}
}

View File

@@ -313,6 +313,15 @@ class Pipeline(EmbedChain):
)
self.connection.commit()
def get_data_sources(self):
db_data = self.cursor.execute("SELECT * FROM data_sources WHERE pipeline_id = ?", (self.local_id,)).fetchall()
data_sources = []
for data in db_data:
data_sources.append({"data_type": data[2], "data_value": data[3], "metadata": data[4]})
return data_sources
def deploy(self):
if self.client is None:
self._init_client()
@@ -348,7 +357,7 @@ class Pipeline(EmbedChain):
with open(yaml_path, "r") as file:
config_data = yaml.safe_load(file)
pipeline_config_data = config_data.get("pipeline", {}).get("config", {})
pipeline_config_data = config_data.get("app", {}).get("config", {})
db_config_data = config_data.get("vectordb", {})
embedding_model_config_data = config_data.get("embedding_model", {})
llm_config_data = config_data.get("llm", {})

View File

@@ -0,0 +1,4 @@
.env
app.db
configs/**.yaml
db

4
examples/rest-api/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.env
app.db
configs/**.yaml
db

View File

@@ -0,0 +1,15 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
EXPOSE 8000
ENV NAME embedchain
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,21 @@
## Single command to rule them all,
```bash
docker run -d --name embedchain -p 8000:8000 embedchain/app:rest-api-latest
```
### To run the app locally,
```bash
# will help reload on changes
DEVELOPMENT=True && python -m main
```
Using docker (locally),
```bash
docker build -t embedchain/app:rest-api-latest .
docker run -d --name embedchain -p 8000:8000 embedchain/app:rest-api-latest
docker image push embedchain/app:rest-api-latest
```

View File

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "ec-rest-api",
"type": "collection"
}

View File

@@ -0,0 +1,18 @@
meta {
name: default_add
type: http
seq: 3
}
post {
url: http://localhost:8000/add
body: json
auth: none
}
body:json {
{
"source": "source_url",
"data_type": "data_type"
}
}

View File

@@ -0,0 +1,17 @@
meta {
name: default_chat
type: http
seq: 4
}
post {
url: http://localhost:8000/chat
body: json
auth: none
}
body:json {
{
"message": "message"
}
}

View File

@@ -0,0 +1,17 @@
meta {
name: default_query
type: http
seq: 2
}
post {
url: http://localhost:8000/query
body: json
auth: none
}
body:json {
{
"query": "Who is Elon Musk?"
}
}

View File

@@ -0,0 +1,11 @@
meta {
name: ping
type: http
seq: 1
}
get {
url: http://localhost:8000/ping
body: json
auth: none
}

View File

@@ -0,0 +1,3 @@
### Config directory
Here, all the YAML files will get stored.

View File

@@ -0,0 +1,11 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URI = "sqlite:///./app.db"
engine = create_engine(SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

View File

@@ -0,0 +1,17 @@
app:
config:
id: 'default'
llm:
provider: gpt4all
config:
model: 'orca-mini-3b.ggmlv3.q4_0.bin'
temperature: 0.5
max_tokens: 1000
top_p: 1
stream: false
embedder:
provider: gpt4all
config:
model: 'all-MiniLM-L6-v2'

321
examples/rest-api/main.py Normal file
View File

@@ -0,0 +1,321 @@
import os
import yaml
from fastapi import FastAPI, UploadFile, Depends, HTTPException
from sqlalchemy.orm import Session
from embedchain import Pipeline as App
from embedchain.client import Client
from models import (
QueryApp,
SourceApp,
DefaultResponse,
DeployAppRequest,
)
from database import Base, engine, SessionLocal
from services import get_app, save_app, get_apps, remove_app
from utils import generate_error_message_for_api_keys
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
app = FastAPI(
title="Embedchain REST API",
description="This is the REST API for Embedchain.",
version="0.0.1",
license_info={
"name": "Apache 2.0",
"url": "https://github.com/embedchain/embedchain/blob/main/LICENSE",
},
)
@app.get("/ping", tags=["Utility"])
def check_status():
"""
Endpoint to check the status of the API.
"""
return {"ping": "pong"}
@app.get("/apps", tags=["Apps"])
async def get_all_apps(db: Session = Depends(get_db)):
"""
Get all apps.
"""
apps = get_apps(db)
return {"results": apps}
@app.post("/create", tags=["Apps"], response_model=DefaultResponse)
async def create_app_using_default_config(app_id: str, config: UploadFile = None, db: Session = Depends(get_db)):
"""
Create a new app using App ID.
If you don't provide a config file, Embedchain will use the default config file\n
which uses opensource GPT4ALL model.\n
app_id: The ID of the app.\n
config: The YAML config file to create an App.\n
"""
try:
if app_id is None:
raise HTTPException(detail="App ID not provided.", status_code=400)
if get_app(db, app_id) is not None:
raise HTTPException(detail=f"App with id '{app_id}' already exists.", status_code=400)
yaml_path = "default.yaml"
if config is not None:
contents = await config.read()
try:
yaml.safe_load(contents)
# TODO: validate the config yaml file here
yaml_path = f"configs/{app_id}.yaml"
with open(yaml_path, "w") as file:
file.write(str(contents, "utf-8"))
except yaml.YAMLError as exc:
raise HTTPException(detail=f"Error parsing YAML: {exc}", status_code=400)
save_app(db, app_id, yaml_path)
return DefaultResponse(response=f"App created successfully. App ID: {app_id}")
except Exception as e:
raise HTTPException(detail=f"Error creating app: {e}", status_code=400)
@app.get(
"/{app_id}/data",
tags=["Apps"],
)
async def get_datasources_associated_with_app_id(app_id: str, db: Session = Depends(get_db)):
"""
Get all datasources for an app.\n
app_id: The ID of the app. Use "default" for the default app.\n
"""
try:
if app_id is None:
raise HTTPException(
detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.",
status_code=400,
)
db_app = get_app(db, app_id)
if db_app is None:
raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400)
app = App.from_config(yaml_path=db_app.config)
response = app.get_data_sources()
return {"results": response}
except ValueError as ve:
if "OPENAI_API_KEY" in str(ve) or "OPENAI_ORGANIZATION" in str(ve):
raise HTTPException(
detail=generate_error_message_for_api_keys(ve),
status_code=400,
)
except Exception as e:
raise HTTPException(detail=f"Error occurred: {e}", status_code=400)
@app.post(
"/{app_id}/add",
tags=["Apps"],
response_model=DefaultResponse,
)
async def add_datasource_to_an_app(body: SourceApp, app_id: str, db: Session = Depends(get_db)):
"""
Add a source to an existing app.\n
app_id: The ID of the app. Use "default" for the default app.\n
source: The source to add.\n
data_type: The data type of the source. Remove it if you want Embedchain to detect it automatically.\n
"""
try:
if app_id is None:
raise HTTPException(
detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.",
status_code=400,
)
db_app = get_app(db, app_id)
if db_app is None:
raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400)
app = App.from_config(yaml_path=db_app.config)
response = app.add(source=body.source, data_type=body.data_type)
return DefaultResponse(response=response)
except ValueError as ve:
if "OPENAI_API_KEY" in str(ve) or "OPENAI_ORGANIZATION" in str(ve):
raise HTTPException(
detail=generate_error_message_for_api_keys(ve),
status_code=400,
)
except Exception as e:
raise HTTPException(detail=f"Error occurred: {e}", status_code=400)
@app.post(
"/{app_id}/query",
tags=["Apps"],
response_model=DefaultResponse,
)
async def query_an_app(body: QueryApp, app_id: str, db: Session = Depends(get_db)):
"""
Query an existing app.\n
app_id: The ID of the app. Use "default" for the default app.\n
query: The query that you want to ask the App.\n
"""
try:
if app_id is None:
raise HTTPException(
detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.",
status_code=400,
)
db_app = get_app(db, app_id)
if db_app is None:
raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400)
app = App.from_config(yaml_path=db_app.config)
response = app.query(body.query)
return DefaultResponse(response=response)
except ValueError as ve:
if "OPENAI_API_KEY" in str(ve) or "OPENAI_ORGANIZATION" in str(ve):
raise HTTPException(
detail=generate_error_message_for_api_keys(ve),
status_code=400,
)
except Exception as e:
raise HTTPException(detail=f"Error occurred: {e}", status_code=400)
# FIXME: The chat implementation of Embedchain needs to be modified to work with the REST API.
# @app.post(
# "/{app_id}/chat",
# tags=["Apps"],
# response_model=DefaultResponse,
# )
# async def chat_with_an_app(body: MessageApp, app_id: str, db: Session = Depends(get_db)):
# """
# Query an existing app.\n
# app_id: The ID of the app. Use "default" for the default app.\n
# message: The message that you want to send to the App.\n
# """
# try:
# if app_id is None:
# raise HTTPException(
# detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.",
# status_code=400,
# )
# db_app = get_app(db, app_id)
# if db_app is None:
# raise HTTPException(
# detail=f"App with id {app_id} does not exist, please create it first.",
# status_code=400
# )
# app = App.from_config(yaml_path=db_app.config)
# response = app.chat(body.message)
# return DefaultResponse(response=response)
# except ValueError as ve:
# if "OPENAI_API_KEY" in str(ve) or "OPENAI_ORGANIZATION" in str(ve):
# raise HTTPException(
# detail=generate_error_message_for_api_keys(ve),
# status_code=400,
# )
# except Exception as e:
# raise HTTPException(detail=f"Error occurred: {e}", status_code=400)
@app.post(
"/{app_id}/deploy",
tags=["Apps"],
response_model=DefaultResponse,
)
async def deploy_app(body: DeployAppRequest, app_id: str, db: Session = Depends(get_db)):
"""
Query an existing app.\n
app_id: The ID of the app. Use "default" for the default app.\n
api_key: The API key to use for deployment. If not provided,
Embedchain will use the API key previously used (if any).\n
"""
try:
if app_id is None:
raise HTTPException(
detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.",
status_code=400,
)
db_app = get_app(db, app_id)
if db_app is None:
raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400)
app = App.from_config(yaml_path=db_app.config)
api_key = body.api_key
# this will save the api key in the embedchain.db
Client(api_key=api_key)
app.deploy()
return DefaultResponse(response="App deployed successfully.")
except ValueError as ve:
if "OPENAI_API_KEY" in str(ve) or "OPENAI_ORGANIZATION" in str(ve):
raise HTTPException(
detail=generate_error_message_for_api_keys(ve),
status_code=400,
)
except Exception as e:
raise HTTPException(detail=f"Error occurred: {e}", status_code=400)
@app.delete(
"/{app_id}/delete",
tags=["Apps"],
response_model=DefaultResponse,
)
async def delete_app(app_id: str, db: Session = Depends(get_db)):
"""
Delete an existing app.\n
app_id: The ID of the app to be deleted.
"""
try:
if app_id is None:
raise HTTPException(
detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.",
status_code=400,
)
db_app = get_app(db, app_id)
if db_app is None:
raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400)
app = App.from_config(yaml_path=db_app.config)
# reset app.db
app.db.reset()
remove_app(db, app_id)
return DefaultResponse(response=f"App with id {app_id} deleted successfully.")
except Exception as e:
raise HTTPException(detail=f"Error occurred: {e}", status_code=400)
if __name__ == "__main__":
import uvicorn
is_dev = os.getenv("DEVELOPMENT", "False")
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=bool(is_dev))

View File

@@ -0,0 +1,45 @@
from typing import Optional
from pydantic import BaseModel, Field
from sqlalchemy import Column, String, Integer
from database import Base
class QueryApp(BaseModel):
query: str = Field("", description="The query that you want to ask the App.")
model_config = {
"json_schema_extra": {
"example": {
"query": "Who is Elon Musk?",
}
}
}
class SourceApp(BaseModel):
source: str = Field("", description="The source that you want to add to the App.")
data_type: Optional[str] = Field("", description="The type of data to add, remove it for autosense.")
model_config = {"json_schema_extra": {"example": {"source": "https://en.wikipedia.org/wiki/Elon_Musk"}}}
class DeployAppRequest(BaseModel):
api_key: str = Field("", description="The Embedchain API key for App deployments.")
model_config = {"json_schema_extra": {"example": {"api_key": "ec-xxx"}}}
class MessageApp(BaseModel):
message: str = Field("", description="The message that you want to send to the App.")
class DefaultResponse(BaseModel):
response: str
class AppModel(Base):
__tablename__ = "apps"
id = Column(Integer, primary_key=True, index=True)
app_id = Column(String, unique=True, index=True)
config = Column(String, unique=True, index=True)

View File

@@ -0,0 +1,5 @@
fastapi==0.104.0
uvicorn==0.23.2
embedchain==0.0.86
embedchain[dataloaders]==0.0.86
sqlalchemy==2.0.22

View File

@@ -0,0 +1,33 @@
app:
config:
id: 'default-app'
llm:
provider: openai
config:
model: 'gpt-3.5-turbo'
temperature: 0.5
max_tokens: 1000
top_p: 1
stream: false
template: |
Use the following pieces of context to answer the query at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
$context
Query: $query
Helpful Answer:
vectordb:
provider: chroma
config:
collection_name: 'rest-api-app'
dir: db
allow_reset: true
embedder:
provider: openai
config:
model: 'text-embedding-ada-002'

View File

@@ -0,0 +1,26 @@
from sqlalchemy.orm import Session
from models import AppModel
def get_app(db: Session, app_id: str):
return db.query(AppModel).filter(AppModel.app_id == app_id).first()
def get_apps(db: Session, skip: int = 0, limit: int = 100):
return db.query(AppModel).offset(skip).limit(limit).all()
def save_app(db: Session, app_id: str, config: str):
db_app = AppModel(app_id=app_id, config=config)
db.add(db_app)
db.commit()
db.refresh(db_app)
return db_app
def remove_app(db: Session, app_id: str):
db_app = db.query(AppModel).filter(AppModel.app_id == app_id).first()
db.delete(db_app)
db.commit()
return db_app

View File

@@ -0,0 +1,21 @@
def generate_error_message_for_api_keys(error: ValueError) -> str:
env_mapping = {
"OPENAI_API_KEY": "OPENAI_API_KEY",
"OPENAI_API_TYPE": "OPENAI_API_TYPE",
"OPENAI_API_BASE": "OPENAI_API_BASE",
"OPENAI_API_VERSION": "OPENAI_API_VERSION",
"COHERE_API_KEY": "COHERE_API_KEY",
"ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY",
"JINACHAT_API_KEY": "JINACHAT_API_KEY",
"HUGGINGFACE_ACCESS_TOKEN": "HUGGINGFACE_ACCESS_TOKEN",
"REPLICATE_API_TOKEN": "REPLICATE_API_TOKEN",
}
missing_keys = [env_mapping[key] for key in env_mapping if key in str(error)]
if missing_keys:
missing_keys_str = ", ".join(missing_keys)
return f"""Please set the {missing_keys_str} environment variable(s) when running the Docker container.
Example: `docker run -e {missing_keys[0]}=xxx embedchain/app:rest-api-latest`
"""
else:
return "Unknown error occurred."