Bläddra i källkod

[Feature] Add `render.com` deployment template (#1033)

Co-authored-by: Deven Patel <iamdevenpatel@gmail.com>
Sidharth Mohanty 1 år sedan
förälder
incheckning
737837ae0b

+ 93 - 0
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
+
+<CodeGroup>
+```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
+```
+</CodeGroup>
+
+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`.
+
+<img src="/images/fly_io.png" />
+
+## Seeking help?
+
+If you run into issues with deployment, please feel free to reach out to us via any of the following methods:
+
+<Snippet file="get-help.mdx" />

+ 1 - 0
docs/get-started/deployment.mdx

@@ -8,6 +8,7 @@ After successfully setting up and testing your RAG app locally, the next step is
 <CardGroup cols={4}>
   <Card title="Fly.io" href="/deployment/fly_io"></Card>
   <Card title="Modal.com" href="/deployment/modal_com"></Card>
+  <Card title="Render.com" href="/deployment/render_com"></Card>
   <Card title="Embedchain Platform" href="#option-1-deploy-on-embedchain-platform"></Card>
   <Card title="Self-hosting" href="#option-2-self-hosting"></Card>
 </CardGroup>

+ 2 - 1
docs/mint.json

@@ -88,7 +88,8 @@
       "pages": [
         "get-started/deployment",
         "deployment/fly_io",
-        "deployment/modal_com"
+        "deployment/modal_com",
+        "deployment/render_com"
       ]
     },
     {

+ 73 - 8
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: \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 `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`
+                  """
+    )
 
 
 @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]")

+ 1 - 0
embedchain/deployment/render.com/.env.example

@@ -0,0 +1 @@
+OPENAI_API_KEY=sk-xxx

+ 1 - 0
embedchain/deployment/render.com/.gitignore

@@ -0,0 +1 @@
+.env

+ 53 - 58
embedchain/deployment/render.com/app.py

@@ -1,58 +1,53 @@
-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>"
+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")

+ 16 - 0
embedchain/deployment/render.com/render.yaml

@@ -0,0 +1,16 @@
+services:
+  - type: web
+    name: ec-render-app
+    runtime: python
+    repo: https://github.com/<your-username>/<repo-name>
+    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

+ 3 - 4
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