Skip to content

Commit

Permalink
Add fastagency as an app type option (#48)
Browse files Browse the repository at this point in the history
* WIP: Add fastagency option

* WIP: Update nginx and docker files

* Polish deployment related issues

* Rename port variable

* Remove comment line
  • Loading branch information
kumaranvpl authored Nov 21, 2024
1 parent e5dda4b commit 719dd18
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
python-version: ["3.12", "3.11", "3.10"]
app-type: ["fastapi+mesop", "mesop", "nats+fastapi+mesop"]
app-type: ["fastapi+mesop", "mesop", "nats+fastapi+mesop", "fastapi"]
authentication: ["basic", "google", "none"]
fail-fast: false
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion cookiecutter.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"project_name": "My FastAgency App",
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}",
"app_type": ["fastapi+mesop", "mesop", "nats+fastapi+mesop"],
"app_type": ["fastapi+mesop", "mesop", "nats+fastapi+mesop", "fastapi"],
"python_version": ["3.12", "3.11", "3.10"],
"authentication": ["basic", "google", "none"]
}
1 change: 1 addition & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{% if 'fastapi' in cookiecutter.app_type %}"{{cookiecutter.project_slug}}/deployment/main.py",{% endif %}
{% if 'nats' not in cookiecutter.app_type %}"{{cookiecutter.project_slug}}/deployment/main_2_fastapi.py",{% endif %}
{% if 'nats' not in cookiecutter.app_type %}".devcontainer/nats_server.conf",{% endif %}
{% if cookiecutter.app_type == 'fastapi' %}"{{cookiecutter.project_slug}}/deployment/main_2_mesop.py",{% endif %}
]

for path in REMOVE_PATHS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ map $fly_machine_id $sticky_action {

# Main server block
server {
listen $MESOP_PORT;
listen $SERVICE_PORT;
server_name localhost;

# Security headers
Expand Down
23 changes: 19 additions & 4 deletions {{cookiecutter.project_slug}}/docker/content/run_fastagency.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ NATS_FASTAPI_PORT=${NATS_FASTAPI_PORT:-8000}
{% if "fastapi" in cookiecutter.app_type %}
FASTAPI_PORT=${FASTAPI_PORT:-8008}
{% endif %}
{% if "mesop" in cookiecutter.app_type %}
export MESOP_PORT=${MESOP_PORT:-8888}

export SERVICE_PORT=$MESOP_PORT
{% else %}
export SERVICE_PORT=$FASTAPI_PORT
{% endif %}
# Default number of workers if not set
WORKERS=${WORKERS:-1}
echo "Number of workers: $WORKERS"
Expand All @@ -20,10 +24,10 @@ echo "Fly machine ID: $FLY_MACHINE_ID"
# Generate nginx config
for ((i=1; i<$WORKERS+1; i++))
do
PORT=$((MESOP_PORT + i))
PORT=$((SERVICE_PORT + i))
sed -i "5i\ server 127.0.0.1:$PORT;" nginx.conf.template
done
envsubst '${MESOP_PORT},${FLY_MACHINE_ID}' < nginx.conf.template >/etc/nginx/conf.d/default.conf
envsubst '${SERVICE_PORT},${FLY_MACHINE_ID}' < nginx.conf.template >/etc/nginx/conf.d/default.conf
echo "Nginx config:"
cat /etc/nginx/conf.d/default.conf

Expand All @@ -33,14 +37,25 @@ nginx -g "daemon off;" &
# Run nats uvicorn server
uvicorn {{cookiecutter.project_slug}}.deployment.main_1_nats:app --host 0.0.0.0 --port $NATS_FASTAPI_PORT > /dev/stdout 2>&1 &
{% endif %}
{% if cookiecutter.app_type == "fastapi" %}
# Run uvicorn server
# Start multiple single-worker uvicorn instances on consecutive ports
for ((i=1; i<$WORKERS+1; i++))
do
PORT=$((SERVICE_PORT + i))
echo "Starting gunicorn on port $PORT"
uvicorn {{cookiecutter.project_slug}}.deployment.main_1_fastapi:app --workers=1 --host 0.0.0.0 --port $PORT > /dev/stdout 2>&1 &
done
{% else %}
# Run uvicorn server
uvicorn {{cookiecutter.project_slug}}.deployment.main_{% if "nats" in cookiecutter.app_type %}2_fastapi{% elif "fastapi" in cookiecutter.app_type %}1_fastapi{% endif %}:app --host 0.0.0.0 --port $FASTAPI_PORT > /dev/stdout 2>&1 &
{% endif %}
{% if "mesop" in cookiecutter.app_type %}
# Run gunicorn server
# Start multiple single-worker gunicorn instances on consecutive ports
for ((i=1; i<$WORKERS+1; i++))
do
PORT=$((MESOP_PORT + i))
PORT=$((SERVICE_PORT + i))
echo "Starting gunicorn on port $PORT"
gunicorn --workers=1 {{cookiecutter.project_slug}}.deployment.main{% if "nats" in cookiecutter.app_type %}_3_mesop{% elif "fastapi" in cookiecutter.app_type %}_2_mesop{% endif %}:app --bind 0.0.0.0:$PORT > /dev/stdout 2>&1 &
done
Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ primary_region = 'ams'
dockerfile = 'docker/Dockerfile'

[http_service]
internal_port = 8888
internal_port = {% if cookiecutter.app_type == 'fastapi' %}8008{% else %}8888{% endif %}
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/scripts/run_docker.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

docker run -it -e OPENAI_API_KEY=$OPENAI_API_KEY {% if cookiecutter.authentication == "google"%}-e GOOGLE_APPLICATION_CREDENTIALS="serviceAccountKey.json"{% endif %} {% if "nats" in cookiecutter.app_type %}-e NATS_URL=$NATS_URL -e FASTAGENCY_NATS_PASSWORD=$FASTAGENCY_NATS_PASSWORD -p 8000:8000{% endif %}{% if "fastapi" in cookiecutter.app_type %} -p 8008:8008{% endif %} -p 8888:8888 {% if "nats" in cookiecutter.app_type %}--network=host{% endif %} deploy_fastagency
docker run -it -e OPENAI_API_KEY=$OPENAI_API_KEY {% if cookiecutter.authentication == "google"%}-e GOOGLE_APPLICATION_CREDENTIALS="serviceAccountKey.json"{% endif %} {% if "nats" in cookiecutter.app_type %}-e NATS_URL=$NATS_URL -e FASTAGENCY_NATS_PASSWORD=$FASTAGENCY_NATS_PASSWORD -p 8000:8000{% endif %}{% if "fastapi" in cookiecutter.app_type %} -p 8008:8008{% endif %}{% if "mesop" in cookiecutter.app_type %} -p 8888:8888{% endif %}{% if "nats" in cookiecutter.app_type %} --network=host{% endif %} deploy_fastagency
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{% if "nats" in cookiecutter.app_type -%}
import os
{% endif %}
{%- if "mesop" in cookiecutter.app_type %}from typing import Any{% endif %}
{%- if "mesop" in cookiecutter.app_type or "fastapi" in cookiecutter.app_type %}from typing import Any{% endif %}

{% if "nats" in cookiecutter.app_type %}from fastagency.adapters.nats import NatsAdapter{% else %}from fastagency.adapters.fastapi import FastAPIAdapter{% endif %}
from fastapi import FastAPI
from fastapi import FastAPI{% if cookiecutter.app_type == 'fastapi' %}
from fastapi.responses import HTMLResponse{%- endif %}

from ..workflow import wf

Expand All @@ -25,12 +26,111 @@
app.include_router(adapter.router)
{%- endif %}

{% if cookiecutter.app_type == 'fastapi' %}
html = """
<!DOCTYPE html>
<html>
<head>
<title>FastAgency Chat App</title>
</head>
<body>
<h1>FastAgency Chat App</h1>
<div id="workflows"></div>
<ul id="messages"></ul>
<script>
const API_URL = location.protocol + '//' + location.host + '/fastagency';
const WS_URL = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/fastagency/ws'; // nosemgrep
let socket;
async function fetchWorkflows() {
const response = await fetch(`${API_URL}/discovery`);
const workflows = await response.json();
const container = document.getElementById('workflows');
workflows.forEach(workflow => {
const button = document.createElement('button');
button.textContent = workflow.description;
button.onclick = () => startWorkflow(workflow.name);
container.appendChild(button);
});
}
async function startWorkflow(name) {
const payload = {
workflow_name: name,
workflow_uuid: generateUUID(),
user_id: null, // Set to null for single-user applications; otherwise, provide the appropriate user ID
params: {}
};
const response = await fetch(`${API_URL}/initiate_workflow`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const workflowJson = await response.json();
connectWebSocket(workflowJson);
}
function connectWebSocket(workflowJson) {
socket = new WebSocket(WS_URL);
socket.onopen = () => {
const initMessage = {
name: workflowJson.name,
workflow_uuid: workflowJson.workflow_uuid,
user_id: workflowJson.user_id,
params: {}
};
socket.send(JSON.stringify(initMessage));
};
socket.onmessage = (event) => handleMessage(JSON.parse(event.data));
}
function handleMessage(message) {
const messagesList = document.getElementById('messages');
const li = document.createElement('li');
if (message.type === 'text_input') {
const response = prompt(message.content.prompt);
socket.send(response);
li.textContent = `${message.sender} -> ${message.recipient}: ${message.content.prompt}`;
} else {
li.textContent = `${message.sender} -> ${message.recipient}: ${message.content?.body || message?.type || JSON.stringify(message)}`;
}
messagesList.appendChild(li);
}
fetchWorkflows();
// Helper function for generating UUID
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
if (c === 'x') {
return (Math.random() * 16 | 0).toString(16);
} else {
return (Math.random() * 16 | 0 & 0x3 | 0x8).toString(16);
}
});
}
</script>
</body>
</html>
"""


# Optional basic HTML page to interact with the workflows
@app.get("/")
async def get() -> HTMLResponse:
return HTMLResponse(html)


# this is optional, but we would like to see the list of available workflows
@app.get("/workflows")
def list_workflows() -> dict[str, Any]:
return {"Workflows": {name: wf.get_description(name) for name in wf.names}}
{% else %}
# this is optional, but we would like to see the list of available workflows
@app.get("/")
def list_workflows() -> dict[str, Any]:
return {"Workflows": {name: wf.get_description(name) for name in wf.names}}

{% endif %}

# start the adapter with the following command
# uvicorn {{cookiecutter.project_slug}}.deployment.main_1_{% if "nats" in cookiecutter.app_type %}nats{% else %}fastapi{% endif %}:app --reload

0 comments on commit 719dd18

Please sign in to comment.