[Deployment] Setup fly.io deployment method and update docs (#1028)
Co-authored-by: sidmohanty11 <sidmohanty11@gmail.com>
This commit is contained in:
215
embedchain/cli.py
Normal file
215
embedchain/cli.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
import pkg_resources
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
def setup_fly_io_app(extra_args):
|
||||
fly_launch_command = ["fly", "launch", "--region", "sjc", "--no-deploy"] + list(extra_args)
|
||||
try:
|
||||
console.print(f"🚀 [bold cyan]Running: {' '.join(fly_launch_command)}[/bold cyan]")
|
||||
subprocess.run(fly_launch_command, check=True)
|
||||
console.print("✅ [bold green]'fly launch' executed successfully.[/bold green]")
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
|
||||
except FileNotFoundError:
|
||||
console.print(
|
||||
"❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]"
|
||||
)
|
||||
|
||||
|
||||
def setup_modal_com_app(extra_args):
|
||||
modal_setup_file = os.path.join(os.path.expanduser("~"), ".modal.toml")
|
||||
if os.path.exists(modal_setup_file):
|
||||
console.print(
|
||||
"""✅ [bold green]Modal setup already done. You can now install the dependencies by doing \n
|
||||
`pip install -r requirements.txt`[/bold green]"""
|
||||
)
|
||||
return
|
||||
modal_setup_cmd = ["modal", "setup"] + list(extra_args)
|
||||
console.print(f"🚀 [bold cyan]Running: {' '.join(modal_setup_cmd)}[/bold cyan]")
|
||||
subprocess.run(modal_setup_cmd, check=True)
|
||||
shutil.move(".env.example", ".env")
|
||||
console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--template", default="fly.io", help="The template to use.")
|
||||
@click.argument("extra_args", nargs=-1, type=click.UNPROCESSED)
|
||||
def create(template, extra_args):
|
||||
try:
|
||||
# Determine the installation location of the embedchain package
|
||||
package_path = pkg_resources.resource_filename("embedchain", "")
|
||||
except ImportError:
|
||||
console.print("❌ [bold red]Failed to locate the 'embedchain' package. Is it installed?[/bold red]")
|
||||
return
|
||||
|
||||
# Construct the source path from the embedchain package
|
||||
src_path = os.path.join(package_path, "deployment", template)
|
||||
|
||||
if not os.path.exists(src_path):
|
||||
console.print(f"❌ [bold red]Template '{template}' not found.[/bold red]")
|
||||
return
|
||||
|
||||
shutil.copytree(src_path, os.getcwd(), dirs_exist_ok=True)
|
||||
env_sample_path = os.path.join(src_path, ".env.example")
|
||||
if os.path.exists(env_sample_path):
|
||||
shutil.copy(env_sample_path, os.path.join(os.getcwd(), ".env"))
|
||||
console.print(f"✅ [bold green]Successfully created app from template '{template}'.[/bold green]")
|
||||
|
||||
if template == "fly.io":
|
||||
setup_fly_io_app(extra_args)
|
||||
elif template == "modal.com":
|
||||
setup_modal_com_app(extra_args)
|
||||
else:
|
||||
raise ValueError(f"Unknown template '{template}'.")
|
||||
|
||||
embedchain_config = {"provider": template}
|
||||
with open("embedchain.json", "w") as file:
|
||||
json.dump(embedchain_config, file, indent=4)
|
||||
console.print(
|
||||
f"🎉 [green]All done! Successfully created `embedchain.json` with '{template}' as provider.[/green]"
|
||||
)
|
||||
|
||||
|
||||
def run_dev_fly_io(debug, host, port):
|
||||
uvicorn_command = ["uvicorn", "app:app"]
|
||||
|
||||
if debug:
|
||||
uvicorn_command.append("--reload")
|
||||
|
||||
uvicorn_command.extend(["--host", host, "--port", str(port)])
|
||||
|
||||
try:
|
||||
console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]")
|
||||
subprocess.run(uvicorn_command, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
|
||||
|
||||
|
||||
def run_dev_modal_com():
|
||||
modal_run_cmd = ["modal", "serve", "app"]
|
||||
try:
|
||||
console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(modal_run_cmd)}[/bold cyan]")
|
||||
subprocess.run(modal_run_cmd, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--debug", is_flag=True, help="Enable or disable debug mode.")
|
||||
@click.option("--host", default="127.0.0.1", help="The host address to run the FastAPI app on.")
|
||||
@click.option("--port", default=8000, help="The port to run the FastAPI app on.")
|
||||
def dev(debug, host, port):
|
||||
template = ""
|
||||
with open("embedchain.json", "r") as file:
|
||||
embedchain_config = json.load(file)
|
||||
template = embedchain_config["provider"]
|
||||
|
||||
if template == "fly.io":
|
||||
run_dev_fly_io(debug, host, port)
|
||||
elif template == "modal.com":
|
||||
run_dev_modal_com()
|
||||
else:
|
||||
raise ValueError(f"Unknown template '{template}'.")
|
||||
|
||||
|
||||
def read_env_file(env_file_path):
|
||||
"""
|
||||
Reads an environment file and returns a dictionary of key-value pairs.
|
||||
|
||||
Args:
|
||||
env_file_path (str): The path to the .env file.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of environment variables.
|
||||
"""
|
||||
env_vars = {}
|
||||
with open(env_file_path, "r") as file:
|
||||
for line in file:
|
||||
# Ignore comments and empty lines
|
||||
if line.strip() and not line.strip().startswith("#"):
|
||||
# Assume each line is in the format KEY=VALUE
|
||||
key_value_match = re.match(r"(\w+)=(.*)", line.strip())
|
||||
if key_value_match:
|
||||
key, value = key_value_match.groups()
|
||||
env_vars[key] = value
|
||||
return env_vars
|
||||
|
||||
|
||||
def deploy_fly():
|
||||
app_name = ""
|
||||
with open("fly.toml", "r") as file:
|
||||
for line in file:
|
||||
if line.strip().startswith("app ="):
|
||||
app_name = line.split("=")[1].strip().strip('"')
|
||||
|
||||
if not app_name:
|
||||
console.print("❌ [bold red]App name not found in fly.toml[/bold red]")
|
||||
return
|
||||
|
||||
env_vars = read_env_file(".env")
|
||||
secrets_command = ["flyctl", "secrets", "set", "-a", app_name] + [f"{k}={v}" for k, v in env_vars.items()]
|
||||
|
||||
deploy_command = ["fly", "deploy"]
|
||||
try:
|
||||
# Set secrets
|
||||
console.print(f"🔐 [bold cyan]Setting secrets for {app_name}[/bold cyan]")
|
||||
subprocess.run(secrets_command, check=True)
|
||||
|
||||
# Deploy application
|
||||
console.print(f"🚀 [bold cyan]Running: {' '.join(deploy_command)}[/bold cyan]")
|
||||
subprocess.run(deploy_command, check=True)
|
||||
console.print("✅ [bold green]'fly deploy' executed successfully.[/bold green]")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
|
||||
except FileNotFoundError:
|
||||
console.print(
|
||||
"❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]"
|
||||
)
|
||||
|
||||
|
||||
def deploy_modal():
|
||||
modal_deploy_cmd = ["modal", "deploy", "app"]
|
||||
try:
|
||||
console.print(f"🚀 [bold cyan]Running: {' '.join(modal_deploy_cmd)}[/bold cyan]")
|
||||
subprocess.run(modal_deploy_cmd, check=True)
|
||||
console.print("✅ [bold green]'modal deploy' executed successfully.[/bold green]")
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
|
||||
except FileNotFoundError:
|
||||
console.print(
|
||||
"❌ [bold red]'modal' command not found. Please ensure Modal CLI is installed and in your PATH.[/bold red]"
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def deploy():
|
||||
# Check for platform-specific files
|
||||
template = ""
|
||||
with open("embedchain.json", "r") as file:
|
||||
embedchain_config = json.load(file)
|
||||
template = embedchain_config["provider"]
|
||||
if template == "fly.io":
|
||||
deploy_fly()
|
||||
elif template == "modal.com":
|
||||
deploy_modal()
|
||||
else:
|
||||
console.print("❌ [bold red]No recognized deployment platform found.[/bold red]")
|
||||
1
embedchain/deployment/fly.io/.dockerignore
Normal file
1
embedchain/deployment/fly.io/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
db/
|
||||
1
embedchain/deployment/fly.io/.env.example
Normal file
1
embedchain/deployment/fly.io/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
OPENAI_API_KEY=sk-xxx
|
||||
13
embedchain/deployment/fly.io/Dockerfile
Normal file
13
embedchain/deployment/fly.io/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt /app/
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . /app
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||
53
embedchain/deployment/fly.io/app.py
Normal file
53
embedchain/deployment/fly.io/app.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from fastapi import FastAPI, responses
|
||||
from pydantic import BaseModel
|
||||
|
||||
from embedchain import Pipeline
|
||||
|
||||
app = FastAPI(title="Embedchain FastAPI App")
|
||||
embedchain_app = Pipeline()
|
||||
|
||||
|
||||
class SourceModel(BaseModel):
|
||||
source: str
|
||||
|
||||
|
||||
class QuestionModel(BaseModel):
|
||||
question: str
|
||||
|
||||
|
||||
@app.post("/add")
|
||||
async def add_source(source_model: SourceModel):
|
||||
"""
|
||||
Adds a new source to the EmbedChain app.
|
||||
Expects a JSON with a "source" key.
|
||||
"""
|
||||
source = source_model.source
|
||||
embedchain_app.add(source)
|
||||
return {"message": f"Source '{source}' added successfully."}
|
||||
|
||||
|
||||
@app.post("/query")
|
||||
async def handle_query(question_model: QuestionModel):
|
||||
"""
|
||||
Handles a query to the EmbedChain app.
|
||||
Expects a JSON with a "question" key.
|
||||
"""
|
||||
question = question_model.question
|
||||
answer = embedchain_app.query(question)
|
||||
return {"answer": answer}
|
||||
|
||||
|
||||
@app.post("/chat")
|
||||
async def handle_chat(question_model: QuestionModel):
|
||||
"""
|
||||
Handles a chat request to the EmbedChain app.
|
||||
Expects a JSON with a "question" key.
|
||||
"""
|
||||
question = question_model.question
|
||||
response = embedchain_app.chat(question)
|
||||
return {"response": response}
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return responses.RedirectResponse(url="/docs")
|
||||
4
embedchain/deployment/fly.io/requirements.txt
Normal file
4
embedchain/deployment/fly.io/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fastapi==0.104.0
|
||||
uvicorn==0.23.2
|
||||
embedchain==0.1.34
|
||||
beautifulsoup4
|
||||
1
embedchain/deployment/modal.com/.env.example
Normal file
1
embedchain/deployment/modal.com/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
OPENAI_API_KEY=
|
||||
1
embedchain/deployment/modal.com/.gitignore
vendored
Normal file
1
embedchain/deployment/modal.com/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.env
|
||||
75
embedchain/deployment/modal.com/app.py
Normal file
75
embedchain/deployment/modal.com/app.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import Body, FastAPI, responses
|
||||
from modal import Image, Secret, Stub, asgi_app
|
||||
|
||||
from embedchain import Pipeline
|
||||
|
||||
load_dotenv(".env")
|
||||
|
||||
image = Image.debian_slim().pip_install(
|
||||
"embedchain",
|
||||
"embedchain[dataloaders]",
|
||||
)
|
||||
|
||||
stub = Stub(
|
||||
name="embedchain-app",
|
||||
image=image,
|
||||
secrets=[Secret.from_dotenv(".env")],
|
||||
)
|
||||
|
||||
web_app = FastAPI()
|
||||
embedchain_app = Pipeline(name="embedchain-modal-app")
|
||||
|
||||
|
||||
@web_app.post("/add")
|
||||
async def add(
|
||||
source: str = Body(..., description="Source to be added"),
|
||||
data_type: str | None = Body(None, description="Type of the data source"),
|
||||
):
|
||||
"""
|
||||
Adds a new source to the EmbedChain app.
|
||||
Expects a JSON with a "source" and "data_type" key.
|
||||
"data_type" is optional.
|
||||
"""
|
||||
if source and data_type:
|
||||
embedchain_app.add(source, data_type)
|
||||
elif source:
|
||||
embedchain_app.add(source)
|
||||
else:
|
||||
return {"message": "No source provided."}
|
||||
return {"message": f"Source '{source}' added successfully."}
|
||||
|
||||
|
||||
@web_app.post("/query")
|
||||
async def query(question: str = Body(..., description="Question to be answered")):
|
||||
"""
|
||||
Handles a query to the EmbedChain app.
|
||||
Expects a JSON with a "question" key.
|
||||
"""
|
||||
if not question:
|
||||
return {"message": "No question provided."}
|
||||
answer = embedchain_app.query(question)
|
||||
return {"answer": answer}
|
||||
|
||||
|
||||
@web_app.get("/chat")
|
||||
async def chat(question: str = Body(..., description="Question to be answered")):
|
||||
"""
|
||||
Handles a chat request to the EmbedChain app.
|
||||
Expects a JSON with a "question" key.
|
||||
"""
|
||||
if not question:
|
||||
return {"message": "No question provided."}
|
||||
response = embedchain_app.chat(question)
|
||||
return {"response": response}
|
||||
|
||||
|
||||
@web_app.get("/")
|
||||
async def root():
|
||||
return responses.RedirectResponse(url="/docs")
|
||||
|
||||
|
||||
@stub.function(image=image)
|
||||
@asgi_app()
|
||||
def fastapi_app():
|
||||
return web_app
|
||||
4
embedchain/deployment/modal.com/requirements.txt
Normal file
4
embedchain/deployment/modal.com/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
modal==0.56.4329
|
||||
fastapi==0.104.0
|
||||
uvicorn==0.23.2
|
||||
embedchain==0.1.34
|
||||
58
embedchain/deployment/render.com/app.py
Normal file
58
embedchain/deployment/render.com/app.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
from embedchain import Pipeline as App
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xxx"
|
||||
|
||||
|
||||
@app.route("/add", methods=["POST"])
|
||||
def add():
|
||||
data = request.get_json()
|
||||
data_type = data.get("data_type")
|
||||
url_or_text = data.get("url_or_text")
|
||||
if data_type and url_or_text:
|
||||
try:
|
||||
App().add(url_or_text, data_type=data_type)
|
||||
return jsonify({"data": f"Added {data_type}: {url_or_text}"}), 200
|
||||
except Exception:
|
||||
logging.exception(f"Failed to add {data_type=}: {url_or_text=}")
|
||||
return jsonify({"error": f"Failed to add {data_type}: {url_or_text}"}), 500
|
||||
return jsonify({"error": "Invalid request. Please provide 'data_type' and 'url_or_text' in JSON format."}), 400
|
||||
|
||||
|
||||
@app.route("/query", methods=["POST"])
|
||||
def query():
|
||||
data = request.get_json()
|
||||
question = data.get("question")
|
||||
if question:
|
||||
try:
|
||||
response = App().query(question)
|
||||
return jsonify({"data": response}), 200
|
||||
except Exception:
|
||||
logging.exception(f"Failed to query {question=}")
|
||||
return jsonify({"error": "An error occurred. Please try again!"}), 500
|
||||
return jsonify({"error": "Invalid request. Please provide 'question' in JSON format."}), 400
|
||||
|
||||
|
||||
@app.route("/chat", methods=["POST"])
|
||||
def chat():
|
||||
data = request.get_json()
|
||||
question = data.get("question")
|
||||
if question:
|
||||
try:
|
||||
response = App().chat(question)
|
||||
return jsonify({"data": response}), 200
|
||||
except Exception:
|
||||
logging.exception(f"Failed to chat {question=}")
|
||||
return jsonify({"error": "An error occurred. Please try again!"}), 500
|
||||
return jsonify({"error": "Invalid request. Please provide 'question' in JSON format."}), 400
|
||||
|
||||
|
||||
@app.route("/api/python")
|
||||
def hello_world():
|
||||
return "<p>Hello, World!</p>"
|
||||
5
embedchain/deployment/render.com/requirements.txt
Normal file
5
embedchain/deployment/render.com/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
numpy==1.24.3
|
||||
Flask==2.2.2
|
||||
Werkzeug==2.2.2
|
||||
gunicorn
|
||||
embedchain
|
||||
Reference in New Issue
Block a user