diff --git a/docs/deployment/render_com.mdx b/docs/deployment/render_com.mdx new file mode 100644 index 00000000..81ba7f6d --- /dev/null +++ b/docs/deployment/render_com.mdx @@ -0,0 +1,93 @@ +--- +title: 'Render.com' +description: 'Deploy your RAG application to render.com platform' +--- + +Embedchain has a nice and simple abstraction on top of the [render.com](https://render.com/) tools to let developers deploy RAG application to render.com platform seamlessly. + +Follow the instructions given below to deploy your first application quickly: + +## Step-1: Install `render` command line + + +```bash OSX +brew tap render-oss/render +brew install render +``` + +```bash Linux +# Make sure you have deno installed -> https://docs.render.com/docs/cli#from-source-unsupported-operating-systems +git clone https://github.com/render-oss/render-cli +cd render-cli +make deps +deno task run +deno compile +``` + +```bash Windows +choco install rendercli +``` + + +In case you run into issues, refer to official [render.com docs](https://docs.render.com/docs/cli). + +## Step-2 Create RAG application: + +We provide a command line utility called `ec` in embedchain that inherits the template for `render.com` platform and help you deploy the app. Follow the instructions to create a render.com app using the template provided: + + +```bash Create application +pip install embedchain +mkdir my-rag-app +ec create --template=render.com +``` + +This `create` command will open a browser window and ask you to login to your render.com account and will generate a directory structure like this: + +```bash +├── app.py +├── .env +├── render.yaml +├── embedchain.json +└── requirements.txt +``` + +Feel free to edit the files as required. +- `app.py`: Contains API app code +- `.env`: Contains environment variables for production +- `render.yaml`: Contains render.com specific configuration for deployment (configure this according to your needs, follow [this](https://docs.render.com/docs/blueprint-spec) for more info) +- `embedchain.json`: Contains embedchain specific configuration for deployment (you don't need to configure this) +- `requirements.txt`: Contains python dependencies for your application + +## Step-3: Test app locally + +You can run the app locally by simply doing: + +```bash Run locally +pip install -r requirements.txt +ec dev +``` + +## Step-4: Deploy to render.com + +Before deploying to render.com, you only have to set up one thing. + +In the render.yaml file, make sure to modify the repo key by inserting the URL of your Git repository where your application will be hosted. You can create a repository from [GitHub](https://github.com) or [GitLab](https://gitlab.com/users/sign_in). + +After that, you're ready to deploy on render.com. + +```bash Deploy app +ec deploy +``` + +When you run this, it should open up your render dashboard and you can see the app being deployed. You can find your hosted link over there only. + +You can also check the logs, monitor app status etc on their dashboard by running command `render dashboard`. + + + +## Seeking help? + +If you run into issues with deployment, please feel free to reach out to us via any of the following methods: + + diff --git a/docs/get-started/deployment.mdx b/docs/get-started/deployment.mdx index 180938c8..3312dc15 100644 --- a/docs/get-started/deployment.mdx +++ b/docs/get-started/deployment.mdx @@ -8,6 +8,7 @@ After successfully setting up and testing your RAG app locally, the next step is + diff --git a/docs/mint.json b/docs/mint.json index 99223132..57c0aa6a 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -88,7 +88,8 @@ "pages": [ "get-started/deployment", "deployment/fly_io", - "deployment/modal_com" + "deployment/modal_com", + "deployment/render_com" ] }, { diff --git a/embedchain/cli.py b/embedchain/cli.py index ba555b19..11bab215 100644 --- a/embedchain/cli.py +++ b/embedchain/cli.py @@ -43,6 +43,7 @@ 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]") + shutil.move(".env.example", ".env") subprocess.run(fly_launch_command, check=True) console.print("✅ [bold green]'fly launch' executed successfully.[/bold green]") except subprocess.CalledProcessError as e: @@ -60,12 +61,41 @@ def setup_modal_com_app(extra_args): """✅ [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) + else: + 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`") + console.print( + """Great! Now you can install the dependencies by doing: \n + `pip install -r requirements.txt`\n + \n + To run your app locally:\n + `ec dev` + """ + ) + + +def setup_render_com_app(): + render_setup_file = os.path.join(os.path.expanduser("~"), ".render/config.yaml") + if os.path.exists(render_setup_file): + console.print( + """✅ [bold green]Render setup already done. You can now install the dependencies by doing \n + `pip install -r requirements.txt`[/bold green]""" + ) + else: + render_setup_cmd = ["render", "config", "init"] + console.print(f"🚀 [bold cyan]Running: {' '.join(render_setup_cmd)}[/bold cyan]") + subprocess.run(render_setup_cmd, check=True) + shutil.move(".env.example", ".env") + console.print( + """Great! Now you can install the dependencies by doing: \n + `pip install -r requirements.txt`\n + \n + To run your app locally:\n + `ec dev` + """ + ) @cli.command() @@ -75,15 +105,14 @@ def create(template, extra_args): anonymous_telemetry.capture(event_name="ec_create", properties={"template_used": template}) src_path = get_pkg_path_from_name(template) 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) + elif template == "render.com": + setup_render_com_app() else: raise ValueError(f"Unknown template '{template}'.") @@ -123,6 +152,23 @@ def run_dev_modal_com(): console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") +def run_dev_render_com(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]") + + @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.") @@ -138,6 +184,8 @@ def dev(debug, host, port): run_dev_fly_io(debug, host, port) elif template == "modal.com": run_dev_modal_com() + elif template == "render.com": + run_dev_render_com(debug, host, port) else: raise ValueError(f"Unknown template '{template}'.") @@ -212,6 +260,21 @@ def deploy_modal(): ) +def deploy_render(): + render_deploy_cmd = ["render", "blueprint", "launch"] + + try: + console.print(f"🚀 [bold cyan]Running: {' '.join(render_deploy_cmd)}[/bold cyan]") + subprocess.run(render_deploy_cmd, check=True) + console.print("✅ [bold green]'render blueprint 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]'render' command not found. Please ensure Render CLI is installed and in your PATH.[/bold red]" + ) + + @cli.command() def deploy(): # Check for platform-specific files @@ -225,5 +288,7 @@ def deploy(): deploy_fly() elif template == "modal.com": deploy_modal() + elif template == "render.com": + deploy_render() else: console.print("❌ [bold red]No recognized deployment platform found.[/bold red]") diff --git a/embedchain/deployment/render.com/.env.example b/embedchain/deployment/render.com/.env.example new file mode 100644 index 00000000..b29363f9 --- /dev/null +++ b/embedchain/deployment/render.com/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY=sk-xxx \ No newline at end of file diff --git a/embedchain/deployment/render.com/.gitignore b/embedchain/deployment/render.com/.gitignore new file mode 100644 index 00000000..4c49bd78 --- /dev/null +++ b/embedchain/deployment/render.com/.gitignore @@ -0,0 +1 @@ +.env diff --git a/embedchain/deployment/render.com/app.py b/embedchain/deployment/render.com/app.py index 9d109465..659e9a9c 100644 --- a/embedchain/deployment/render.com/app.py +++ b/embedchain/deployment/render.com/app.py @@ -1,58 +1,53 @@ -import logging -import os +from fastapi import FastAPI, responses +from pydantic import BaseModel -from flask import Flask, jsonify, request +from embedchain import Pipeline -from embedchain import Pipeline as App - -app = Flask(__name__) - -os.environ["OPENAI_API_KEY"] = "sk-xxx" +app = FastAPI(title="Embedchain FastAPI App") +embedchain_app = Pipeline() -@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 +class SourceModel(BaseModel): + source: str -@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 +class QuestionModel(BaseModel): + question: str -@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.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.route("/api/python") -def hello_world(): - return "

Hello, World!

" +@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") diff --git a/embedchain/deployment/render.com/render.yaml b/embedchain/deployment/render.com/render.yaml new file mode 100644 index 00000000..04ec5048 --- /dev/null +++ b/embedchain/deployment/render.com/render.yaml @@ -0,0 +1,16 @@ +services: + - type: web + name: ec-render-app + runtime: python + repo: https://github.com// + scaling: + minInstances: 1 + maxInstances: 3 + targetMemoryPercent: 60 # optional if targetCPUPercent is set + targetCPUPercent: 60 # optional if targetMemory is set + buildCommand: pip install -r requirements.txt + startCommand: uvicorn app:app --host 0.0.0.0 + envVars: + - key: OPENAI_API_KEY + value: sk-xxx + autoDeploy: false # optional diff --git a/embedchain/deployment/render.com/requirements.txt b/embedchain/deployment/render.com/requirements.txt index 5141b904..3a768929 100644 --- a/embedchain/deployment/render.com/requirements.txt +++ b/embedchain/deployment/render.com/requirements.txt @@ -1,5 +1,4 @@ -numpy==1.24.3 -Flask==2.2.2 -Werkzeug==2.2.2 -gunicorn +fastapi==0.104.0 +uvicorn==0.23.2 embedchain +beautifulsoup4 \ No newline at end of file